Skip to content
Snippets Groups Projects
Commit 491301d8 authored by Emil's avatar Emil
Browse files

Merge branch 'dev' of...

Merge branch 'dev' of https://gitlab.liu.se/tddd96-grupp11/teknikattan-scoring-system into 143-implement-editor-views
parents 61d47b9a 5404d2dc
No related branches found
No related tags found
1 merge request!105Resolve "Implement editor views"
Pipeline #42813 passed with warnings
Showing
with 261 additions and 96 deletions
...@@ -5,11 +5,11 @@ This file handles actions for the presentation redux state ...@@ -5,11 +5,11 @@ This file handles actions for the presentation redux state
import axios from 'axios' import axios from 'axios'
import { Slide } from '../interfaces/ApiModels' import { Slide } from '../interfaces/ApiModels'
import { Timer } from '../interfaces/Timer' import { Timer } from '../interfaces/Timer'
import store, { AppDispatch } from './../store' import store, { AppDispatch, RootState } from './../store'
import Types from './types' import Types from './types'
// Save competition in presentation state from input id // Save competition in presentation state from input id
export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch) => { export const getPresentationCompetition = (id: string) => async (dispatch: AppDispatch, getState: () => RootState) => {
await axios await axios
.get(`/api/competitions/${id}`) .get(`/api/competitions/${id}`)
.then((res) => { .then((res) => {
...@@ -17,6 +17,9 @@ export const getPresentationCompetition = (id: string) => async (dispatch: AppDi ...@@ -17,6 +17,9 @@ export const getPresentationCompetition = (id: string) => async (dispatch: AppDi
type: Types.SET_PRESENTATION_COMPETITION, type: Types.SET_PRESENTATION_COMPETITION,
payload: res.data, payload: res.data,
}) })
if (getState().presentation.slide.id === -1 && res.data.slides[0]) {
setCurrentSlideByOrder(0)(dispatch)
}
}) })
.catch((err) => { .catch((err) => {
console.log(err) console.log(err)
......
...@@ -54,7 +54,6 @@ export interface Team extends NameID { ...@@ -54,7 +54,6 @@ export interface Team extends NameID {
export interface Question extends NameID { export interface Question extends NameID {
slide_id: number slide_id: number
title: string
total_score: number total_score: number
type_id: number type_id: number
} }
......
...@@ -6,10 +6,6 @@ import Drawer from '@material-ui/core/Drawer' ...@@ -6,10 +6,6 @@ import Drawer from '@material-ui/core/Drawer'
import ListItemText from '@material-ui/core/ListItemText' import ListItemText from '@material-ui/core/ListItemText'
import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles' import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles'
import AddOutlinedIcon from '@material-ui/icons/AddOutlined' import AddOutlinedIcon from '@material-ui/icons/AddOutlined'
import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined'
import CreateOutlinedIcon from '@material-ui/icons/CreateOutlined'
import DnsOutlinedIcon from '@material-ui/icons/DnsOutlined'
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'
import axios from 'axios' import axios from 'axios'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Link, useParams } from 'react-router-dom' import { Link, useParams } from 'react-router-dom'
...@@ -18,6 +14,7 @@ import { getEditorCompetition, setEditorSlideId, setEditorViewId } from '../../a ...@@ -18,6 +14,7 @@ import { getEditorCompetition, setEditorSlideId, setEditorViewId } from '../../a
import { getTypes } from '../../actions/typesAction' import { getTypes } from '../../actions/typesAction'
import { useAppDispatch, useAppSelector } from '../../hooks' import { useAppDispatch, useAppSelector } from '../../hooks'
import { RichSlide } from '../../interfaces/ApiRichModels' import { RichSlide } from '../../interfaces/ApiRichModels'
import { renderSlideIcon } from '../../utils/renderSlideIcon'
import { RemoveMenuItem } from '../admin/styledComp' import { RemoveMenuItem } from '../admin/styledComp'
import { Content, InnerContent } from '../views/styled' import { Content, InnerContent } from '../views/styled'
import SettingsPanel from './components/SettingsPanel' import SettingsPanel from './components/SettingsPanel'
...@@ -141,21 +138,6 @@ const PresentationEditorPage: React.FC = () => { ...@@ -141,21 +138,6 @@ const PresentationEditorPage: React.FC = () => {
setContextState(initialState) setContextState(initialState)
} }
const renderSlideIcon = (slide: RichSlide) => {
if (slide.questions && slide.questions[0] && slide.questions[0].type_id) {
switch (slide.questions[0].type_id) {
case 1:
return <CreateOutlinedIcon /> // text question
case 2:
return <BuildOutlinedIcon /> // practical qustion
case 3:
return <DnsOutlinedIcon /> // multiple choice question
}
} else {
return <InfoOutlinedIcon /> // information slide
}
}
const GreenCheckbox = withStyles({ const GreenCheckbox = withStyles({
root: { root: {
color: '#FFFFFF', color: '#FFFFFF',
......
...@@ -15,7 +15,7 @@ const SlideDisplay = ({ variant, activeViewTypeId }: SlideDisplayProps) => { ...@@ -15,7 +15,7 @@ const SlideDisplay = ({ variant, activeViewTypeId }: SlideDisplayProps) => {
const components = useAppSelector((state) => { const components = useAppSelector((state) => {
if (variant === 'editor') if (variant === 'editor')
return state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId)?.components return state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId)?.components
return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide.id)?.components return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide?.id)?.components
}) })
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const editorPaperRef = useRef<HTMLDivElement>(null) const editorPaperRef = useRef<HTMLDivElement>(null)
......
...@@ -9,7 +9,7 @@ import JudgeViewPage from './JudgeViewPage' ...@@ -9,7 +9,7 @@ import JudgeViewPage from './JudgeViewPage'
it('renders judge view page', () => { it('renders judge view page', () => {
const compRes: any = { const compRes: any = {
data: { data: {
slides: [{ id: 0, title: '' }], slides: [{ id: 0, title: '', questions: [{ id: 0 }] }],
}, },
} }
const teamsRes: any = { const teamsRes: any = {
......
import { Divider, List, ListItemText, Typography } from '@material-ui/core' import { Card, Divider, List, ListItem, ListItemText, Paper, Typography } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { getPresentationCompetition, setCurrentSlide, setPresentationCode } from '../../actions/presentation' import { getPresentationCompetition, setCurrentSlide, setPresentationCode } from '../../actions/presentation'
...@@ -18,8 +18,13 @@ import { ...@@ -18,8 +18,13 @@ import {
JudgeToolbar, JudgeToolbar,
LeftDrawer, LeftDrawer,
RightDrawer, RightDrawer,
ScoreHeaderPadding,
ScoreHeaderPaper,
ScoreFooterPadding,
} from './styled' } from './styled'
import SlideDisplay from '../presentationEditor/components/SlideDisplay' import SlideDisplay from '../presentationEditor/components/SlideDisplay'
import JudgeScoringInstructions from './components/JudgeScoringInstructions'
import { renderSlideIcon } from '../../utils/renderSlideIcon'
const leftDrawerWidth = 150 const leftDrawerWidth = 150
const rightDrawerWidth = 700 const rightDrawerWidth = 700
...@@ -50,6 +55,7 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { ...@@ -50,6 +55,7 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => {
const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Judge')?.id const activeViewTypeId = viewTypes.find((viewType) => viewType.name === 'Judge')?.id
const teams = useAppSelector((state) => state.presentation.competition.teams) const teams = useAppSelector((state) => state.presentation.competition.teams)
const slides = useAppSelector((state) => state.presentation.competition.slides) const slides = useAppSelector((state) => state.presentation.competition.slides)
const currentQuestion = slides[activeSlideIndex]?.questions[0]
const handleSelectSlide = (index: number) => { const handleSelectSlide = (index: number) => {
setActiveSlideIndex(index) setActiveSlideIndex(index)
dispatch(setCurrentSlide(slides[index])) dispatch(setCurrentSlide(slides[index]))
...@@ -88,8 +94,8 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { ...@@ -88,8 +94,8 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => {
button button
key={slide.id} key={slide.id}
> >
<Typography variant="h6">Slide ID: {slide.id} </Typography> {renderSlideIcon(slide)}
<ListItemText primary={slide.title} /> <ListItemText primary={`Sida ${slide.order + 1}`} />
</SlideListItem> </SlideListItem>
))} ))}
</List> </List>
...@@ -103,7 +109,13 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { ...@@ -103,7 +109,13 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => {
anchor="right" anchor="right"
> >
<div className={classes.toolbar} /> <div className={classes.toolbar} />
<List> {currentQuestion && (
<ScoreHeaderPaper $rightDrawerWidth={rightDrawerWidth} elevation={4}>
<Typography variant="h4">{`${currentQuestion.name} (${currentQuestion.total_score}p)`}</Typography>
</ScoreHeaderPaper>
)}
<ScoreHeaderPadding />
<List style={{ overflowY: 'scroll', overflowX: 'hidden' }}>
{teams && {teams &&
teams.map((answer, index) => ( teams.map((answer, index) => (
<div key={answer.name}> <div key={answer.name}>
...@@ -112,8 +124,10 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => { ...@@ -112,8 +124,10 @@ const JudgeViewPage = ({ competitionId, code }: JudgeViewPageProps) => {
</div> </div>
))} ))}
</List> </List>
<ScoreFooterPadding />
<JudgeScoringInstructions question={currentQuestion} />
</RightDrawer> </RightDrawer>
<div style={{ height: 64 }} /> <div className={classes.toolbar} />
<Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}> <Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}>
<InnerContent> <InnerContent>
{activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />} {activeViewTypeId && <SlideDisplay variant="presentation" activeViewTypeId={activeViewTypeId} />}
......
import { Box, Typography } from '@material-ui/core' import { Box, Typography } from '@material-ui/core'
import axios from 'axios'
import React from 'react' import React from 'react'
import { useAppSelector } from '../../../hooks' import { getPresentationCompetition } from '../../../actions/presentation'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { AnswerContainer, ScoreDisplayContainer, ScoreDisplayHeader, ScoreInput } from './styled' import { AnswerContainer, ScoreDisplayContainer, ScoreDisplayHeader, ScoreInput } from './styled'
type ScoreDisplayProps = { type ScoreDisplayProps = {
teamIndex: number teamIndex: number
} }
const questionMaxScore = 5
const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => { const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => {
const dispatch = useAppDispatch()
const currentTeam = useAppSelector((state) => state.presentation.competition.teams[teamIndex]) const currentTeam = useAppSelector((state) => state.presentation.competition.teams[teamIndex])
const currentCompetititonId = useAppSelector((state) => state.presentation.competition.id)
const activeQuestion = useAppSelector(
(state) =>
state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide?.id)?.questions[0]
)
const scores = currentTeam.question_answers.map((questionAnswer) => questionAnswer.score)
const questionMaxScore = activeQuestion?.total_score
const activeAnswer = currentTeam.question_answers.find(
(questionAnswer) => questionAnswer.question_id === activeQuestion?.id
)
const handleEditScore = async (newScore: number, answerId: number) => {
await axios
.put(`/api/competitions/${currentCompetititonId}/teams/${currentTeam.id}/answers/${answerId}`, {
score: newScore,
})
.then(() => dispatch(getPresentationCompetition(currentCompetititonId.toString())))
}
return ( return (
<ScoreDisplayContainer> <ScoreDisplayContainer>
<ScoreDisplayHeader> <ScoreDisplayHeader>
...@@ -17,21 +37,25 @@ const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => { ...@@ -17,21 +37,25 @@ const JudgeScoreDisplay = ({ teamIndex }: ScoreDisplayProps) => {
<Box fontWeight="fontWeightBold">{currentTeam.name}</Box> <Box fontWeight="fontWeightBold">{currentTeam.name}</Box>
</Typography> </Typography>
<ScoreInput {activeAnswer && (
label="Poäng" <ScoreInput
defaultValue={0} label="Poäng"
inputProps={{ style: { fontSize: 20 } }} defaultValue={activeAnswer?.score}
InputProps={{ disableUnderline: true, inputProps: { min: 0, max: questionMaxScore } }} inputProps={{ style: { fontSize: 20 } }}
type="number" InputProps={{ disableUnderline: true, inputProps: { min: 0, max: questionMaxScore } }}
></ScoreInput> type="number"
onChange={(event) => handleEditScore(+event.target.value, activeAnswer.id)}
/>
)}
</ScoreDisplayHeader> </ScoreDisplayHeader>
<Typography variant="h6">Alla poäng: 2 0 0 0 0 0 0 0 0</Typography> <Typography variant="h6">Alla poäng: [ {scores.map((score) => `${score} `)}]</Typography>
<Typography variant="h6">Total poäng: 9</Typography> <Typography variant="h6">Total poäng: {scores.reduce((a, b) => a + b, 0)}</Typography>
<AnswerContainer> {activeAnswer && (
<Typography variant="body1"> <AnswerContainer>
Svar: blablablablablablablablablabla blablablablabla blablablablabla blablablablablablablablablabla{' '} <Typography variant="body1">{activeAnswer.answer}</Typography>
</Typography> </AnswerContainer>
</AnswerContainer> )}
{!activeAnswer && <Typography variant="body1">Inget svar</Typography>}
</ScoreDisplayContainer> </ScoreDisplayContainer>
) )
} }
......
import { Box, Card, Typography } from '@material-ui/core'
import axios from 'axios'
import React from 'react'
import { getPresentationCompetition } from '../../../actions/presentation'
import { useAppDispatch, useAppSelector } from '../../../hooks'
import { RichQuestion } from '../../../interfaces/ApiRichModels'
import {
AnswerContainer,
JudgeScoringInstructionsContainer,
ScoreDisplayContainer,
ScoreDisplayHeader,
ScoreInput,
} from './styled'
type JudgeScoringInstructionsProps = {
question: RichQuestion
}
const JudgeScoringInstructions = ({ question }: JudgeScoringInstructionsProps) => {
return (
<JudgeScoringInstructionsContainer elevation={3}>
<Typography variant="h4">Rättningsinstruktioner</Typography>
<Typography variant="body1">Såhär rättar du denhär frågan</Typography>
</JudgeScoringInstructionsContainer>
)
}
export default JudgeScoringInstructions
...@@ -37,6 +37,7 @@ const PresentationComponent = ({ component, width, height, scale }: Presentation ...@@ -37,6 +37,7 @@ const PresentationComponent = ({ component, width, height, scale }: Presentation
minWidth={75 * scale} minWidth={75 * scale}
minHeight={75 * scale} minHeight={75 * scale}
disableDragging={true} disableDragging={true}
enableResizing={false}
bounds="parent" bounds="parent"
//Multiply by scale to show components correctly for current screen size //Multiply by scale to show components correctly for current screen size
size={{ width: component.w * scale, height: component.h * scale }} size={{ width: component.w * scale, height: component.h * scale }}
......
import { TextField } from '@material-ui/core' import { Card, Paper, TextField } from '@material-ui/core'
import styled from 'styled-components' import styled from 'styled-components'
export const SlideContainer = styled.div` export const SlideContainer = styled.div`
...@@ -32,3 +32,13 @@ export const AnswerContainer = styled.div` ...@@ -32,3 +32,13 @@ export const AnswerContainer = styled.div`
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
` `
export const JudgeScoringInstructionsContainer = styled(Paper)`
position: absolute;
bottom: 0;
height: 250px;
width: 100%;
display: flex;
align-items: center;
flex-direction: column;
`
import { AppBar, Button, Drawer, Toolbar, Typography } from '@material-ui/core' import { AppBar, Button, Card, Drawer, Paper, Toolbar, Typography } from '@material-ui/core'
import styled from 'styled-components' import styled from 'styled-components'
export const JudgeAppBar = styled(AppBar)` export const JudgeAppBar = styled(AppBar)`
...@@ -146,3 +146,26 @@ export const PresenterInnerContent = styled.div` ...@@ -146,3 +146,26 @@ export const PresenterInnerContent = styled.div`
export const ParticipantContainer = styled.div` export const ParticipantContainer = styled.div`
max-width: calc((100vh / 9) * 16); max-width: calc((100vh / 9) * 16);
` `
interface ScoreHeaderPaperProps {
$rightDrawerWidth: number
}
export const ScoreHeaderPaper = styled(Card)<ScoreHeaderPaperProps>`
position: absolute;
top: 66px;
width: ${(props) => (props ? props.$rightDrawerWidth : 0)}px;
height: 71px;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
`
export const ScoreHeaderPadding = styled.div`
min-height: 71px;
`
export const ScoreFooterPadding = styled.div`
min-height: 250px;
`
...@@ -6,7 +6,8 @@ import presentationReducer from './presentationReducer' ...@@ -6,7 +6,8 @@ import presentationReducer from './presentationReducer'
const initialState = { const initialState = {
competition: { competition: {
name: '', name: '',
id: 0, id: 1,
background_image: undefined,
city_id: 0, city_id: 0,
slides: [], slides: [],
year: 0, year: 0,
...@@ -14,7 +15,8 @@ const initialState = { ...@@ -14,7 +15,8 @@ const initialState = {
}, },
slide: { slide: {
competition_id: 0, competition_id: 0,
id: 0, background_image: undefined,
id: -1,
order: 0, order: 0,
timer: 0, timer: 0,
title: '', title: '',
...@@ -48,7 +50,7 @@ it('should handle SET_PRESENTATION_COMPETITION', () => { ...@@ -48,7 +50,7 @@ it('should handle SET_PRESENTATION_COMPETITION', () => {
}) })
).toEqual({ ).toEqual({
competition: testCompetition, competition: testCompetition,
slide: testCompetition.slides[0], slide: initialState.slide,
code: initialState.code, code: initialState.code,
timer: initialState.timer, timer: initialState.timer,
}) })
......
...@@ -14,7 +14,7 @@ interface PresentationState { ...@@ -14,7 +14,7 @@ interface PresentationState {
const initialState: PresentationState = { const initialState: PresentationState = {
competition: { competition: {
name: '', name: '',
id: 0, id: 1,
city_id: 0, city_id: 0,
slides: [], slides: [],
year: 0, year: 0,
...@@ -23,7 +23,7 @@ const initialState: PresentationState = { ...@@ -23,7 +23,7 @@ const initialState: PresentationState = {
}, },
slide: { slide: {
competition_id: 0, competition_id: 0,
id: 0, id: -1,
order: 0, order: 0,
timer: 0, timer: 0,
title: '', title: '',
...@@ -41,7 +41,6 @@ export default function (state = initialState, action: AnyAction) { ...@@ -41,7 +41,6 @@ export default function (state = initialState, action: AnyAction) {
case Types.SET_PRESENTATION_COMPETITION: case Types.SET_PRESENTATION_COMPETITION:
return { return {
...state, ...state,
slide: action.payload.slides[0] as Slide,
competition: action.payload as RichCompetition, competition: action.payload as RichCompetition,
} }
case Types.SET_PRESENTATION_CODE: case Types.SET_PRESENTATION_CODE:
......
import { RichSlide } from '../interfaces/ApiRichModels'
import React from 'react'
import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined'
import CreateOutlinedIcon from '@material-ui/icons/CreateOutlined'
import DnsOutlinedIcon from '@material-ui/icons/DnsOutlined'
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'
export const renderSlideIcon = (slide: RichSlide) => {
if (slide.questions && slide.questions[0] && slide.questions[0].type_id) {
switch (slide.questions[0].type_id) {
case 1:
return <CreateOutlinedIcon /> // text question
case 2:
return <BuildOutlinedIcon /> // practical qustion
case 3:
return <DnsOutlinedIcon /> // multiple choice question
}
} else {
return <InfoOutlinedIcon /> // information slide
}
}
...@@ -13,24 +13,62 @@ def validate_editor(db_item, *views): ...@@ -13,24 +13,62 @@ def validate_editor(db_item, *views):
abort(http_codes.UNAUTHORIZED) abort(http_codes.UNAUTHORIZED)
def check_jwt(editor=False, *views): def _is_allowed(allowed, actual):
def wrapper(fn): return actual and "*" in allowed or actual in allowed
@wraps(fn)
def decorator(*args, **kwargs):
def _has_access(in_claim, in_route):
in_route = int(in_route) if in_route else None
return not in_route or in_claim and in_claim == in_route
def protect_route(allowed_roles=None, allowed_views=None):
def wrapper(func):
def inner(*args, **kwargs):
verify_jwt_in_request() verify_jwt_in_request()
claims = get_jwt_claims() claims = get_jwt_claims()
# Authorize request if roles has access to the route #
nonlocal allowed_roles
allowed_roles = allowed_roles or []
role = claims.get("role") role = claims.get("role")
if _is_allowed(allowed_roles, role):
return func(*args, **kwargs)
# Authorize request if view has access and is trying to access the
# competition its in. Also check team if client is a team.
# Allow request if route doesn't belong to any competition.
nonlocal allowed_views
allowed_views = allowed_views or []
view = claims.get("view") view = claims.get("view")
if role == "Admin": if not _is_allowed(allowed_views, view):
return fn(*args, **kwargs) abort(
elif editor and role == "Editor": http_codes.UNAUTHORIZED,
return fn(*args, **kwargs) f"Client with view '{view}' is not allowed to access route with allowed views {allowed_views}.",
elif view in views: )
return fn(*args, **kwargs)
else: claim_competition_id = claims.get("competition_id")
abort(http_codes.UNAUTHORIZED) route_competition_id = kwargs.get("competition_id")
if not _has_access(claim_competition_id, route_competition_id):
return decorator abort(
http_codes.UNAUTHORIZED,
f"Client in competition '{claim_competition_id}' is not allowed to access competition '{route_competition_id}'.",
)
if view == "Team":
claim_team_id = claims.get("team_id")
route_team_id = kwargs.get("team_id")
if not _has_access(claim_team_id, route_team_id):
abort(
http_codes.UNAUTHORIZED,
f"Client in team '{claim_team_id}' is not allowed to access team '{route_team_id}'.",
)
return func(*args, **kwargs)
return inner
return wrapper return wrapper
......
import app.core.http_codes as codes import app.core.http_codes as codes
import app.database.controller as dbc import app.database.controller as dbc
from app.apis import check_jwt, item_response, list_response from app.apis import item_response, list_response, protect_route
from app.core.dto import QuestionAlternativeDTO from app.core.dto import QuestionAlternativeDTO
from flask_restx import Resource from flask_restx import Resource
from flask_restx import reqparse from flask_restx import reqparse
...@@ -17,12 +17,12 @@ question_alternative_parser.add_argument("value", type=int, default=None, locati ...@@ -17,12 +17,12 @@ question_alternative_parser.add_argument("value", type=int, default=None, locati
@api.route("") @api.route("")
@api.param("competition_id, slide_id, question_id") @api.param("competition_id, slide_id, question_id")
class QuestionAlternativeList(Resource): class QuestionAlternativeList(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"], allowed_views=["*"])
def get(self, competition_id, slide_id, question_id): def get(self, competition_id, slide_id, question_id):
items = dbc.get.question_alternative_list(competition_id, slide_id, question_id) items = dbc.get.question_alternative_list(competition_id, slide_id, question_id)
return list_response(list_schema.dump(items)) return list_response(list_schema.dump(items))
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def post(self, competition_id, slide_id, question_id): def post(self, competition_id, slide_id, question_id):
args = question_alternative_parser.parse_args(strict=True) args = question_alternative_parser.parse_args(strict=True)
item = dbc.add.question_alternative(**args, question_id=question_id) item = dbc.add.question_alternative(**args, question_id=question_id)
...@@ -32,19 +32,19 @@ class QuestionAlternativeList(Resource): ...@@ -32,19 +32,19 @@ class QuestionAlternativeList(Resource):
@api.route("/<alternative_id>") @api.route("/<alternative_id>")
@api.param("competition_id, slide_id, question_id, alternative_id") @api.param("competition_id, slide_id, question_id, alternative_id")
class QuestionAlternatives(Resource): class QuestionAlternatives(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"], allowed_views=["*"])
def get(self, competition_id, slide_id, question_id, alternative_id): def get(self, competition_id, slide_id, question_id, alternative_id):
items = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id) items = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
return item_response(schema.dump(items)) return item_response(schema.dump(items))
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def put(self, competition_id, slide_id, question_id, alternative_id): def put(self, competition_id, slide_id, question_id, alternative_id):
args = question_alternative_parser.parse_args(strict=True) args = question_alternative_parser.parse_args(strict=True)
item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id) item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
item = dbc.edit.default(item, **args) item = dbc.edit.default(item, **args)
return item_response(schema.dump(item)) return item_response(schema.dump(item))
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def delete(self, competition_id, slide_id, question_id, alternative_id): def delete(self, competition_id, slide_id, question_id, alternative_id):
item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id) item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
dbc.delete.default(item) dbc.delete.default(item)
......
import app.core.http_codes as codes import app.core.http_codes as codes
import app.database.controller as dbc import app.database.controller as dbc
from app.apis import check_jwt, item_response, list_response from app.apis import item_response, list_response, protect_route
from app.core.dto import QuestionAnswerDTO from app.core.dto import QuestionAnswerDTO
from flask_restx import Resource from flask_restx import Resource
from flask_restx import reqparse from flask_restx import reqparse
...@@ -22,12 +22,12 @@ question_answer_edit_parser.add_argument("score", type=int, default=None, locati ...@@ -22,12 +22,12 @@ question_answer_edit_parser.add_argument("score", type=int, default=None, locati
@api.route("") @api.route("")
@api.param("competition_id, team_id") @api.param("competition_id, team_id")
class QuestionAnswerList(Resource): class QuestionAnswerList(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"], allowed_views=["*"])
def get(self, competition_id, team_id): def get(self, competition_id, team_id):
items = dbc.get.question_answer_list(competition_id, team_id) items = dbc.get.question_answer_list(competition_id, team_id)
return list_response(list_schema.dump(items)) return list_response(list_schema.dump(items))
@check_jwt(editor=True) @protect_route(allowed_roles=["*"], allowed_views=["*"])
def post(self, competition_id, team_id): def post(self, competition_id, team_id):
args = question_answer_parser.parse_args(strict=True) args = question_answer_parser.parse_args(strict=True)
item = dbc.add.question_answer(**args, team_id=team_id) item = dbc.add.question_answer(**args, team_id=team_id)
...@@ -37,12 +37,12 @@ class QuestionAnswerList(Resource): ...@@ -37,12 +37,12 @@ class QuestionAnswerList(Resource):
@api.route("/<answer_id>") @api.route("/<answer_id>")
@api.param("competition_id, team_id, answer_id") @api.param("competition_id, team_id, answer_id")
class QuestionAnswers(Resource): class QuestionAnswers(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"], allowed_views=["*"])
def get(self, competition_id, team_id, answer_id): def get(self, competition_id, team_id, answer_id):
item = dbc.get.question_answer(competition_id, team_id, answer_id) item = dbc.get.question_answer(competition_id, team_id, answer_id)
return item_response(schema.dump(item)) return item_response(schema.dump(item))
@check_jwt(editor=True) @protect_route(allowed_roles=["*"], allowed_views=["*"])
def put(self, competition_id, team_id, answer_id): def put(self, competition_id, team_id, answer_id):
args = question_answer_edit_parser.parse_args(strict=True) args = question_answer_edit_parser.parse_args(strict=True)
item = dbc.get.question_answer(competition_id, team_id, answer_id) item = dbc.get.question_answer(competition_id, team_id, answer_id)
......
import app.core.http_codes as codes import app.core.http_codes as codes
import app.database.controller as dbc import app.database.controller as dbc
from app.apis import check_jwt, item_response, text_response from app.apis import item_response, protect_route, text_response
from app.core.codes import verify_code from app.core.codes import verify_code
from app.core.dto import AuthDTO, CodeDTO from app.core.dto import AuthDTO, CodeDTO
from flask_jwt_extended import ( from flask_jwt_extended import (
...@@ -12,6 +12,8 @@ from flask_jwt_extended import ( ...@@ -12,6 +12,8 @@ from flask_jwt_extended import (
) )
from flask_restx import Resource from flask_restx import Resource
from flask_restx import inputs, reqparse from flask_restx import inputs, reqparse
from datetime import timedelta
from app.core import sockets
api = AuthDTO.api api = AuthDTO.api
schema = AuthDTO.schema schema = AuthDTO.schema
...@@ -33,9 +35,13 @@ def get_user_claims(item_user): ...@@ -33,9 +35,13 @@ def get_user_claims(item_user):
return {"role": item_user.role.name, "city_id": item_user.city_id} return {"role": item_user.role.name, "city_id": item_user.city_id}
def get_code_claims(item_code):
return {"view": item_code.view_type.name, "competition_id": item_code.competition_id, "team_id": item_code.team_id}
@api.route("/signup") @api.route("/signup")
class AuthSignup(Resource): class AuthSignup(Resource):
@check_jwt(editor=False) @protect_route(allowed_roles=["Admin"])
def post(self): def post(self):
args = create_user_parser.parse_args(strict=True) args = create_user_parser.parse_args(strict=True)
email = args.get("email") email = args.get("email")
...@@ -50,7 +56,7 @@ class AuthSignup(Resource): ...@@ -50,7 +56,7 @@ class AuthSignup(Resource):
@api.route("/delete/<ID>") @api.route("/delete/<ID>")
@api.param("ID") @api.param("ID")
class AuthDelete(Resource): class AuthDelete(Resource):
@check_jwt(editor=False) @protect_route(allowed_roles=["Admin"])
def delete(self, ID): def delete(self, ID):
item_user = dbc.get.user(ID) item_user = dbc.get.user(ID)
...@@ -86,24 +92,38 @@ class AuthLoginCode(Resource): ...@@ -86,24 +92,38 @@ class AuthLoginCode(Resource):
code = args["code"] code = args["code"]
if not verify_code(code): if not verify_code(code):
api.abort(codes.BAD_REQUEST, "Invalid code") api.abort(codes.UNAUTHORIZED, "Invalid code")
item_code = dbc.get.code_by_code(code) item_code = dbc.get.code_by_code(code)
return item_response(CodeDTO.schema.dump(item_code))
if item_code.competition_id not in sockets.presentations:
api.abort(codes.UNAUTHORIZED, "Competition not active")
access_token = create_access_token(
item_code.id, user_claims=get_code_claims(item_code), expires_delta=timedelta(hours=8)
)
response = {
"competition_id": item_code.competition_id,
"view_type_id": item_code.view_type_id,
"team_id": item_code.team_id,
"access_token": access_token,
}
return response
@api.route("/logout") @api.route("/logout")
class AuthLogout(Resource): class AuthLogout(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"], allowed_views=["*"])
def post(self): def post(self):
jti = get_raw_jwt()["jti"] jti = get_raw_jwt()["jti"]
dbc.add.blacklist(jti) dbc.add.blacklist(jti)
return text_response("User logout") return text_response("Logout")
@api.route("/refresh") @api.route("/refresh")
class AuthRefresh(Resource): class AuthRefresh(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
@jwt_refresh_token_required @jwt_refresh_token_required
def post(self): def post(self):
old_jti = get_raw_jwt()["jti"] old_jti = get_raw_jwt()["jti"]
......
import app.database.controller as dbc import app.database.controller as dbc
from app.apis import check_jwt, item_response, list_response from app.apis import item_response, list_response, protect_route
from app.core import http_codes as codes from app.core import http_codes as codes
from app.core.dto import CodeDTO from app.core.dto import CodeDTO
from app.database.models import Code from app.database.models import Code
...@@ -13,7 +13,7 @@ list_schema = CodeDTO.list_schema ...@@ -13,7 +13,7 @@ list_schema = CodeDTO.list_schema
@api.route("") @api.route("")
@api.param("competition_id") @api.param("competition_id")
class CodesList(Resource): class CodesList(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def get(self, competition_id): def get(self, competition_id):
items = dbc.get.code_list(competition_id) items = dbc.get.code_list(competition_id)
return list_response(list_schema.dump(items), len(items)) return list_response(list_schema.dump(items), len(items))
...@@ -22,7 +22,7 @@ class CodesList(Resource): ...@@ -22,7 +22,7 @@ class CodesList(Resource):
@api.route("/<code_id>") @api.route("/<code_id>")
@api.param("competition_id, code_id") @api.param("competition_id, code_id")
class CodesById(Resource): class CodesById(Resource):
@check_jwt(editor=False) @protect_route(allowed_roles=["*"])
def put(self, competition_id, code_id): def put(self, competition_id, code_id):
item = dbc.get.one(Code, code_id) item = dbc.get.one(Code, code_id)
item.code = dbc.utils.generate_unique_code() item.code = dbc.utils.generate_unique_code()
......
import time import time
import app.database.controller as dbc import app.database.controller as dbc
from app.apis import check_jwt, item_response, list_response from app.apis import item_response, list_response, protect_route
from app.core.dto import CompetitionDTO from app.core.dto import CompetitionDTO
from app.database.models import Competition from app.database.models import Competition
from flask_restx import Resource from flask_restx import Resource
...@@ -29,7 +29,7 @@ competition_search_parser.add_argument("city_id", type=int, default=None, locati ...@@ -29,7 +29,7 @@ competition_search_parser.add_argument("city_id", type=int, default=None, locati
@api.route("") @api.route("")
class CompetitionsList(Resource): class CompetitionsList(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def post(self): def post(self):
args = competition_parser.parse_args(strict=True) args = competition_parser.parse_args(strict=True)
...@@ -44,20 +44,21 @@ class CompetitionsList(Resource): ...@@ -44,20 +44,21 @@ class CompetitionsList(Resource):
@api.route("/<competition_id>") @api.route("/<competition_id>")
@api.param("competition_id") @api.param("competition_id")
class Competitions(Resource): class Competitions(Resource):
@protect_route(allowed_roles=["*"], allowed_views=["*"])
def get(self, competition_id): def get(self, competition_id):
item = dbc.get.competition(competition_id) item = dbc.get.competition(competition_id)
return item_response(rich_schema.dump(item)) return item_response(rich_schema.dump(item))
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def put(self, competition_id): def put(self, competition_id):
args = competition_edit_parser.parse_args(strict=True) args = competition_edit_parser.parse_args(strict=True)
item = dbc.get.one(Competition, competition_id) item = dbc.get.one(Competition, competition_id)
item = dbc.edit.default(item, **args) item = dbc.edit.competition(item, **args)
return item_response(schema.dump(item)) return item_response(schema.dump(item))
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def delete(self, competition_id): def delete(self, competition_id):
item = dbc.get.one(Competition, competition_id) item = dbc.get.one(Competition, competition_id)
dbc.delete.competition(item) dbc.delete.competition(item)
...@@ -67,7 +68,7 @@ class Competitions(Resource): ...@@ -67,7 +68,7 @@ class Competitions(Resource):
@api.route("/search") @api.route("/search")
class CompetitionSearch(Resource): class CompetitionSearch(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def get(self): def get(self):
args = competition_search_parser.parse_args(strict=True) args = competition_search_parser.parse_args(strict=True)
items, total = dbc.search.competition(**args) items, total = dbc.search.competition(**args)
...@@ -77,7 +78,7 @@ class CompetitionSearch(Resource): ...@@ -77,7 +78,7 @@ class CompetitionSearch(Resource):
@api.route("/<competition_id>/copy") @api.route("/<competition_id>/copy")
@api.param("competition_id") @api.param("competition_id")
class SlidesOrder(Resource): class SlidesOrder(Resource):
@check_jwt(editor=True) @protect_route(allowed_roles=["*"])
def post(self, competition_id): def post(self, competition_id):
item_competition = dbc.get.competition(competition_id) item_competition = dbc.get.competition(competition_id)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment