From d83d66daa4dfc513b1675f2261c0f5875dcf586a Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Thu, 8 Apr 2021 10:54:06 +0200
Subject: [PATCH 01/21] Add editor state in redux

---
 client/src/actions/editor.ts                  | 17 ++++++++++
 client/src/actions/types.ts                   |  1 +
 .../PresentationEditorPage.tsx                | 13 ++++++--
 client/src/reducers/allReducers.ts            |  2 ++
 client/src/reducers/editorReducer.ts          | 31 +++++++++++++++++++
 5 files changed, 61 insertions(+), 3 deletions(-)
 create mode 100644 client/src/actions/editor.ts
 create mode 100644 client/src/reducers/editorReducer.ts

diff --git a/client/src/actions/editor.ts b/client/src/actions/editor.ts
new file mode 100644
index 00000000..cf00921a
--- /dev/null
+++ b/client/src/actions/editor.ts
@@ -0,0 +1,17 @@
+import axios from 'axios'
+import { AppDispatch } from './../store'
+import Types from './types'
+
+export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .get(`/competitions/${id}`)
+    .then((res) => {
+      dispatch({
+        type: Types.SET_EDITOR_COMPETITION,
+        payload: res.data,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts
index 40e94303..bfa3032e 100644
--- a/client/src/actions/types.ts
+++ b/client/src/actions/types.ts
@@ -10,6 +10,7 @@ export default {
   SET_COMPETITIONS_FILTER_PARAMS: 'SET_COMPETITIONS_FILTER_PARAMS',
   SET_COMPETITIONS_TOTAL: 'SET_COMPETITIONS_TOTAL',
   SET_COMPETITIONS_COUNT: 'SET_COMPETITIONS_COUNT',
+  SET_EDITOR_COMPETITION: 'SET_EDITOR_COMPETITION',
   SET_CITIES: 'SET_CITIES',
   SET_CITIES_TOTAL: 'SET_CITIES_TOTAL',
   SET_CITIES_COUNT: 'SET_CITIES_COUNT',
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index 5a0dbc99..6e60aa40 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -5,8 +5,10 @@ import Drawer from '@material-ui/core/Drawer'
 import List from '@material-ui/core/List'
 import ListItemText from '@material-ui/core/ListItemText'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
-import React from 'react'
+import React, { useEffect } from 'react'
 import { useParams } from 'react-router-dom'
+import { getEditorCompetition } from '../../actions/editor'
+import { useAppDispatch, useAppSelector } from '../../hooks'
 import SettingsPanel from './components/SettingsPanel'
 import { SlideListItem, ToolBarContainer, ViewButton, ViewButtonGroup } from './styled'
 
@@ -69,14 +71,19 @@ interface CompetitionParams {
 
 const PresentationEditorPage: React.FC = () => {
   const classes = useStyles()
-  const params: CompetitionParams = useParams()
+  const { id }: CompetitionParams = useParams()
+  const dispatch = useAppDispatch()
+  const competition = useAppSelector((state) => state.editor.competition)
+  useEffect(() => {
+    dispatch(getEditorCompetition(id))
+  }, [])
   return (
     <div className={classes.root}>
       <CssBaseline />
       <AppBar position="fixed" className={classes.appBar}>
         <ToolBarContainer>
           <Typography variant="h6" noWrap>
-            Tävling nr: {params.id}
+            Tävlingsnamn: {competition.name}
           </Typography>
           <ViewButtonGroup>
             <ViewButton variant="contained" color="secondary">
diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts
index b12b5346..732cff39 100644
--- a/client/src/reducers/allReducers.ts
+++ b/client/src/reducers/allReducers.ts
@@ -3,6 +3,7 @@
 import { combineReducers } from 'redux'
 import citiesReducer from './citiesReducer'
 import competitionsReducer from './competitionsReducer'
+import editorReducer from './editorReducer'
 import uiReducer from './uiReducer'
 import userReducer from './userReducer'
 
@@ -12,5 +13,6 @@ const allReducers = combineReducers({
   UI: uiReducer,
   competitions: competitionsReducer,
   cities: citiesReducer,
+  editor: editorReducer,
 })
 export default allReducers
diff --git a/client/src/reducers/editorReducer.ts b/client/src/reducers/editorReducer.ts
new file mode 100644
index 00000000..a7a09367
--- /dev/null
+++ b/client/src/reducers/editorReducer.ts
@@ -0,0 +1,31 @@
+import { AnyAction } from 'redux'
+import Types from '../actions/types'
+import { Competition } from '../interfaces/Competition'
+
+interface EditorState {
+  competition: Competition
+}
+
+const initialState: EditorState = {
+  competition: {
+    name: '',
+    id: 0,
+    year: 0,
+    city: {
+      id: 0,
+      name: '',
+    },
+  },
+}
+
+export default function (state = initialState, action: AnyAction) {
+  switch (action.type) {
+    case Types.SET_EDITOR_COMPETITION:
+      return {
+        ...state,
+        competition: action.payload as Competition,
+      }
+    default:
+      return state
+  }
+}
-- 
GitLab


From 1b2d265d13f150f3bc988768026eb327971359b4 Mon Sep 17 00:00:00 2001
From: Sebastian Karlsson <sebka991@student.liu.se>
Date: Thu, 8 Apr 2021 14:20:25 +0200
Subject: [PATCH 02/21] Add getting slides from database in editor.

---
 client/src/interfaces/Competition.ts              |  2 ++
 client/src/interfaces/Slide.ts                    |  7 +++++++
 .../presentationEditor/PresentationEditorPage.tsx | 15 +++------------
 client/src/reducers/editorReducer.ts              |  1 +
 4 files changed, 13 insertions(+), 12 deletions(-)
 create mode 100644 client/src/interfaces/Slide.ts

diff --git a/client/src/interfaces/Competition.ts b/client/src/interfaces/Competition.ts
index 7a9c7032..aaf51951 100644
--- a/client/src/interfaces/Competition.ts
+++ b/client/src/interfaces/Competition.ts
@@ -1,8 +1,10 @@
 import { City } from './City'
+import { Slide } from './Slide'
 
 export interface Competition {
   name: string
   id: number
   city: City
   year: number
+  slides: Slide[]
 }
diff --git a/client/src/interfaces/Slide.ts b/client/src/interfaces/Slide.ts
new file mode 100644
index 00000000..3706ed7c
--- /dev/null
+++ b/client/src/interfaces/Slide.ts
@@ -0,0 +1,7 @@
+export interface Slide {
+  id: number
+  competition_id: number
+  title: string
+  order: number
+  timer: number
+}
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index 6e60aa40..abcfb399 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -16,15 +16,6 @@ function createSlide(name: string) {
   return { name }
 }
 
-const slides = [
-  createSlide('Sida 1'),
-  createSlide('Sida 2'),
-  createSlide('Sida 3'),
-  createSlide('Sida 4'),
-  createSlide('Sida 5'),
-  createSlide('Sida 6'),
-  createSlide('Sida 7'),
-]
 const leftDrawerWidth = 150
 const rightDrawerWidth = 390
 
@@ -109,9 +100,9 @@ const PresentationEditorPage: React.FC = () => {
         <div className={classes.toolbar} />
         <Divider />
         <List>
-          {slides.map((slide) => (
-            <SlideListItem divider button key={slide.name}>
-              <ListItemText primary={slide.name} />
+          {competition.slides.map((slide) => (
+            <SlideListItem divider button key={slide.title}>
+              <ListItemText primary={slide.title} />
             </SlideListItem>
           ))}
         </List>
diff --git a/client/src/reducers/editorReducer.ts b/client/src/reducers/editorReducer.ts
index a7a09367..eb238298 100644
--- a/client/src/reducers/editorReducer.ts
+++ b/client/src/reducers/editorReducer.ts
@@ -15,6 +15,7 @@ const initialState: EditorState = {
       id: 0,
       name: '',
     },
+    slides: [],
   },
 }
 
-- 
GitLab


From 2064c273573824c3f7ae05dbf8251755499adc6f Mon Sep 17 00:00:00 2001
From: Emil <Emil>
Date: Thu, 8 Apr 2021 22:30:08 +0200
Subject: [PATCH 03/21] Feat: update competitions name in editor.competition

---
 client/src/actions/editor.ts                  | 15 ++++++
 client/src/actions/types.ts                   |  1 +
 .../components/CompetitionSettings.tsx        | 47 +++++++++++++++++--
 client/src/reducers/editorReducer.ts          |  8 ++++
 4 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/client/src/actions/editor.ts b/client/src/actions/editor.ts
index cf00921a..e23cd084 100644
--- a/client/src/actions/editor.ts
+++ b/client/src/actions/editor.ts
@@ -15,3 +15,18 @@ export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch
       console.log(err)
     })
 }
+
+export const setCompetitionName = (id: string, name: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .get(`/competitions/${id}`)
+    .then((res) => {
+      dispatch({
+        type: Types.SET_COMPETITION_NAME,
+        payload: res.data,
+        name: name,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts
index bfa3032e..72441b10 100644
--- a/client/src/actions/types.ts
+++ b/client/src/actions/types.ts
@@ -11,6 +11,7 @@ export default {
   SET_COMPETITIONS_TOTAL: 'SET_COMPETITIONS_TOTAL',
   SET_COMPETITIONS_COUNT: 'SET_COMPETITIONS_COUNT',
   SET_EDITOR_COMPETITION: 'SET_EDITOR_COMPETITION',
+  SET_COMPETITION_NAME: 'SET COMPETITION NAME',
   SET_CITIES: 'SET_CITIES',
   SET_CITIES_TOTAL: 'SET_CITIES_TOTAL',
   SET_CITIES_COUNT: 'SET_CITIES_COUNT',
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
index a9a1d767..ad7595d9 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -1,7 +1,10 @@
 import { Button, Divider, List, ListItem, ListItemText, TextField } from '@material-ui/core'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import CloseIcon from '@material-ui/icons/Close'
-import React, { useState } from 'react'
+import React, { useEffect, useState } from 'react'
+import { useParams } from 'react-router-dom'
+import { getEditorCompetition, setCompetitionName } from '../../../actions/editor'
+import { useAppDispatch, useAppSelector } from '../../../hooks'
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -34,8 +37,31 @@ const useStyles = makeStyles((theme: Theme) =>
   })
 )
 
+interface CompetitionParams {
+  id: string
+}
+
 const CompetitionSettings: React.FC = () => {
   const classes = useStyles()
+  const { id }: CompetitionParams = useParams()
+  const dispatch = useAppDispatch()
+
+  const competition = useAppSelector((state) => state.editor.competition)
+  const competitionName = competition.name
+  useEffect(() => {
+    dispatch(getEditorCompetition(id))
+  }, [])
+
+  const updateCompetitionName = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+    if (timerHandle) {
+      clearTimeout(timerHandle)
+      setTimerHandle(undefined)
+    }
+    //Only updates filter and api 100ms after last input was made
+    setTimerHandle(window.setTimeout(() => dispatch(setCompetitionName(id, event.target.value)), 100))
+    dispatch(setCompetitionName(id, competitionName))
+  }
+
   const initialList = [
     { id: '1', name: 'Lag1' },
     { id: '2', name: 'Lag2' },
@@ -44,13 +70,28 @@ const CompetitionSettings: React.FC = () => {
   const handleClick = (id: string) => {
     setTeams(teams.filter((item) => item.id !== id)) //Will not be done like this when api is used
   }
+  const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined)
   const [teams, setTeams] = useState(initialList)
+
   return (
     <div className={classes.textInputContainer}>
       <form noValidate autoComplete="off">
-        <TextField className={classes.textInput} id="outlined-basic" label="Tävlingsnamn" variant="outlined" />
+        <TextField
+          className={classes.textInput}
+          id="outlined-basic"
+          label={'Tävlingsnamn'}
+          defaultValue={competitionName}
+          onChange={updateCompetitionName}
+          variant="outlined"
+        />
         <Divider />
-        <TextField className={classes.textInput} id="outlined-basic" label="Stad" variant="outlined" />
+        <TextField
+          className={classes.textInput}
+          id="outlined-basic"
+          label="Stad"
+          defaultValue={competition.city.name}
+          variant="outlined"
+        />
       </form>
 
       <List>
diff --git a/client/src/reducers/editorReducer.ts b/client/src/reducers/editorReducer.ts
index eb238298..80cf3a9b 100644
--- a/client/src/reducers/editorReducer.ts
+++ b/client/src/reducers/editorReducer.ts
@@ -26,6 +26,14 @@ export default function (state = initialState, action: AnyAction) {
         ...state,
         competition: action.payload as Competition,
       }
+    case Types.SET_COMPETITION_NAME:
+      return {
+        ...state,
+        competition: {
+          ...state.competition,
+          name: action.name,
+        } as Competition,
+      }
     default:
       return state
   }
-- 
GitLab


From d01725ca7fe7996ff0aed40f58fcbeeeb76cdac0 Mon Sep 17 00:00:00 2001
From: Emil <Emil>
Date: Fri, 9 Apr 2021 00:56:32 +0200
Subject: [PATCH 04/21] removed editor state, added name update to competitions
 state

---
 client/src/actions/competitions.ts            | 15 +++++++
 client/src/actions/editor.ts                  | 32 ---------------
 .../admin/components/CompetitionManager.tsx   |  3 +-
 .../PresentationEditorPage.tsx                | 14 +++----
 .../components/CompetitionSettings.tsx        |  9 ++---
 client/src/reducers/allReducers.ts            |  2 -
 client/src/reducers/competitionsReducer.ts    | 12 ++++++
 client/src/reducers/editorReducer.ts          | 40 -------------------
 8 files changed, 37 insertions(+), 90 deletions(-)
 delete mode 100644 client/src/actions/editor.ts
 delete mode 100644 client/src/reducers/editorReducer.ts

diff --git a/client/src/actions/competitions.ts b/client/src/actions/competitions.ts
index a758fd63..608060ae 100644
--- a/client/src/actions/competitions.ts
+++ b/client/src/actions/competitions.ts
@@ -37,3 +37,18 @@ export const getCompetitions = () => async (dispatch: AppDispatch, getState: ()
 export const setFilterParams = (params: CompetitionFilterParams) => (dispatch: AppDispatch) => {
   dispatch({ type: Types.SET_COMPETITIONS_FILTER_PARAMS, payload: params })
 }
+export const setCompetitionName = (id: string, name: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .get(`/competitions/search`)
+    .then((res) => {
+      dispatch({
+        type: Types.SET_COMPETITION_NAME,
+        payload: res.data.items,
+        name: name,
+        id: +id - 1,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
diff --git a/client/src/actions/editor.ts b/client/src/actions/editor.ts
deleted file mode 100644
index e23cd084..00000000
--- a/client/src/actions/editor.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import axios from 'axios'
-import { AppDispatch } from './../store'
-import Types from './types'
-
-export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch) => {
-  await axios
-    .get(`/competitions/${id}`)
-    .then((res) => {
-      dispatch({
-        type: Types.SET_EDITOR_COMPETITION,
-        payload: res.data,
-      })
-    })
-    .catch((err) => {
-      console.log(err)
-    })
-}
-
-export const setCompetitionName = (id: string, name: string) => async (dispatch: AppDispatch) => {
-  await axios
-    .get(`/competitions/${id}`)
-    .then((res) => {
-      dispatch({
-        type: Types.SET_COMPETITION_NAME,
-        payload: res.data,
-        name: name,
-      })
-    })
-    .catch((err) => {
-      console.log(err)
-    })
-}
diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx
index ff73a994..c9a8b915 100644
--- a/client/src/pages/admin/components/CompetitionManager.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.tsx
@@ -18,6 +18,7 @@ import { Link } from 'react-router-dom'
 import { getCities } from '../../../actions/cities'
 import { getCompetitions, setFilterParams } from '../../../actions/competitions'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
+import { Competition } from '../../../interfaces/Competition'
 import { CompetitionFilterParams } from '../../../interfaces/CompetitionFilterParams'
 import AddCompetition from './AddCompetition'
 import { FilterContainer, RemoveCompetition, TopBar, YearFilterTextField } from './styled'
@@ -145,7 +146,7 @@ const CompetitionManager: React.FC = (props: any) => {
           </TableHead>
           <TableBody>
             {competitions &&
-              competitions.map((row) => (
+              competitions.map((row: Competition) => (
                 <TableRow key={row.name}>
                   <TableCell scope="row">
                     <Button color="primary" component={Link} to={`/editor/competition-id=${row.id}`}>
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index abcfb399..19caef8f 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -5,10 +5,10 @@ import Drawer from '@material-ui/core/Drawer'
 import List from '@material-ui/core/List'
 import ListItemText from '@material-ui/core/ListItemText'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
-import React, { useEffect } from 'react'
+import React from 'react'
 import { useParams } from 'react-router-dom'
-import { getEditorCompetition } from '../../actions/editor'
-import { useAppDispatch, useAppSelector } from '../../hooks'
+import { useAppSelector } from '../../hooks'
+import { Slide } from '../../interfaces/Slide'
 import SettingsPanel from './components/SettingsPanel'
 import { SlideListItem, ToolBarContainer, ViewButton, ViewButtonGroup } from './styled'
 
@@ -63,11 +63,7 @@ interface CompetitionParams {
 const PresentationEditorPage: React.FC = () => {
   const classes = useStyles()
   const { id }: CompetitionParams = useParams()
-  const dispatch = useAppDispatch()
-  const competition = useAppSelector((state) => state.editor.competition)
-  useEffect(() => {
-    dispatch(getEditorCompetition(id))
-  }, [])
+  const competition = useAppSelector((state) => state.competitions.competitions[+id - 1])
   return (
     <div className={classes.root}>
       <CssBaseline />
@@ -100,7 +96,7 @@ const PresentationEditorPage: React.FC = () => {
         <div className={classes.toolbar} />
         <Divider />
         <List>
-          {competition.slides.map((slide) => (
+          {competition.slides.map((slide: Slide) => (
             <SlideListItem divider button key={slide.title}>
               <ListItemText primary={slide.title} />
             </SlideListItem>
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
index ad7595d9..b668010b 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -1,9 +1,9 @@
 import { Button, Divider, List, ListItem, ListItemText, TextField } from '@material-ui/core'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import CloseIcon from '@material-ui/icons/Close'
-import React, { useEffect, useState } from 'react'
+import React, { useState } from 'react'
 import { useParams } from 'react-router-dom'
-import { getEditorCompetition, setCompetitionName } from '../../../actions/editor'
+import { setCompetitionName } from '../../../actions/competitions'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
 
 const useStyles = makeStyles((theme: Theme) =>
@@ -46,11 +46,8 @@ const CompetitionSettings: React.FC = () => {
   const { id }: CompetitionParams = useParams()
   const dispatch = useAppDispatch()
 
-  const competition = useAppSelector((state) => state.editor.competition)
+  const competition = useAppSelector((state) => state.competitions.competitions[+id - 1])
   const competitionName = competition.name
-  useEffect(() => {
-    dispatch(getEditorCompetition(id))
-  }, [])
 
   const updateCompetitionName = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
     if (timerHandle) {
diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts
index 732cff39..b12b5346 100644
--- a/client/src/reducers/allReducers.ts
+++ b/client/src/reducers/allReducers.ts
@@ -3,7 +3,6 @@
 import { combineReducers } from 'redux'
 import citiesReducer from './citiesReducer'
 import competitionsReducer from './competitionsReducer'
-import editorReducer from './editorReducer'
 import uiReducer from './uiReducer'
 import userReducer from './userReducer'
 
@@ -13,6 +12,5 @@ const allReducers = combineReducers({
   UI: uiReducer,
   competitions: competitionsReducer,
   cities: citiesReducer,
-  editor: editorReducer,
 })
 export default allReducers
diff --git a/client/src/reducers/competitionsReducer.ts b/client/src/reducers/competitionsReducer.ts
index b3003d4a..fabf4801 100644
--- a/client/src/reducers/competitionsReducer.ts
+++ b/client/src/reducers/competitionsReducer.ts
@@ -39,6 +39,18 @@ export default function (state = initialState, action: AnyAction) {
         ...state,
         count: action.payload as number,
       }
+    case Types.SET_COMPETITION_NAME:
+      return {
+        ...state,
+        competitions: state.competitions.map((competition, i) =>
+          i === action.id
+            ? ({
+                ...competition,
+                name: action.name as string,
+              } as Competition)
+            : (competition as Competition)
+        ) as Competition[],
+      }
     default:
       return state
   }
diff --git a/client/src/reducers/editorReducer.ts b/client/src/reducers/editorReducer.ts
deleted file mode 100644
index 80cf3a9b..00000000
--- a/client/src/reducers/editorReducer.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { AnyAction } from 'redux'
-import Types from '../actions/types'
-import { Competition } from '../interfaces/Competition'
-
-interface EditorState {
-  competition: Competition
-}
-
-const initialState: EditorState = {
-  competition: {
-    name: '',
-    id: 0,
-    year: 0,
-    city: {
-      id: 0,
-      name: '',
-    },
-    slides: [],
-  },
-}
-
-export default function (state = initialState, action: AnyAction) {
-  switch (action.type) {
-    case Types.SET_EDITOR_COMPETITION:
-      return {
-        ...state,
-        competition: action.payload as Competition,
-      }
-    case Types.SET_COMPETITION_NAME:
-      return {
-        ...state,
-        competition: {
-          ...state.competition,
-          name: action.name,
-        } as Competition,
-      }
-    default:
-      return state
-  }
-}
-- 
GitLab


From 2c587eefd7d4080f664d6778f20a24a7af8688da Mon Sep 17 00:00:00 2001
From: Emil <Emil>
Date: Fri, 9 Apr 2021 14:04:16 +0200
Subject: [PATCH 05/21] feat: update competition name

---
 client/src/actions/competitions.ts            | 15 ------
 client/src/actions/editor.ts                  | 41 ++++++++++++++++
 .../admin/components/CompetitionManager.tsx   |  3 +-
 .../PresentationEditorPage.tsx                | 14 ++++--
 .../components/CompetitionSettings.tsx        | 17 ++++---
 client/src/reducers/allReducers.ts            |  2 +
 client/src/reducers/competitionsReducer.ts    | 12 -----
 client/src/reducers/editorReducer.ts          | 48 +++++++++++++++++++
 8 files changed, 112 insertions(+), 40 deletions(-)
 create mode 100644 client/src/actions/editor.ts
 create mode 100644 client/src/reducers/editorReducer.ts

diff --git a/client/src/actions/competitions.ts b/client/src/actions/competitions.ts
index 608060ae..a758fd63 100644
--- a/client/src/actions/competitions.ts
+++ b/client/src/actions/competitions.ts
@@ -37,18 +37,3 @@ export const getCompetitions = () => async (dispatch: AppDispatch, getState: ()
 export const setFilterParams = (params: CompetitionFilterParams) => (dispatch: AppDispatch) => {
   dispatch({ type: Types.SET_COMPETITIONS_FILTER_PARAMS, payload: params })
 }
-export const setCompetitionName = (id: string, name: string) => async (dispatch: AppDispatch) => {
-  await axios
-    .get(`/competitions/search`)
-    .then((res) => {
-      dispatch({
-        type: Types.SET_COMPETITION_NAME,
-        payload: res.data.items,
-        name: name,
-        id: +id - 1,
-      })
-    })
-    .catch((err) => {
-      console.log(err)
-    })
-}
diff --git a/client/src/actions/editor.ts b/client/src/actions/editor.ts
new file mode 100644
index 00000000..683e716a
--- /dev/null
+++ b/client/src/actions/editor.ts
@@ -0,0 +1,41 @@
+import axios from 'axios'
+import { AppDispatch } from './../store'
+import Types from './types'
+
+export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .get(`/competitions/${id}`)
+    .then((res) => {
+      dispatch({
+        type: Types.SET_EDITOR_COMPETITION,
+        payload: res.data,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+
+export const setCompetitionName = (id: string, name: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .get(`/competitions/${id}`)
+    .then((res) => {
+      dispatch({
+        type: Types.SET_COMPETITION_NAME,
+        payload: res.data,
+        name: name,
+      })
+    })
+    .catch((err) => {
+      console.log(err)
+    })
+}
+
+export const submitCompetitionName = (id: string, name: string) => async (dispatch: AppDispatch) => {
+  await axios
+    .put(`/competitions/${id}`, { name: name })
+    .then(() => {
+      dispatch(getEditorCompetition(id))
+    })
+    .catch(console.log)
+}
diff --git a/client/src/pages/admin/components/CompetitionManager.tsx b/client/src/pages/admin/components/CompetitionManager.tsx
index c9a8b915..ff73a994 100644
--- a/client/src/pages/admin/components/CompetitionManager.tsx
+++ b/client/src/pages/admin/components/CompetitionManager.tsx
@@ -18,7 +18,6 @@ import { Link } from 'react-router-dom'
 import { getCities } from '../../../actions/cities'
 import { getCompetitions, setFilterParams } from '../../../actions/competitions'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
-import { Competition } from '../../../interfaces/Competition'
 import { CompetitionFilterParams } from '../../../interfaces/CompetitionFilterParams'
 import AddCompetition from './AddCompetition'
 import { FilterContainer, RemoveCompetition, TopBar, YearFilterTextField } from './styled'
@@ -146,7 +145,7 @@ const CompetitionManager: React.FC = (props: any) => {
           </TableHead>
           <TableBody>
             {competitions &&
-              competitions.map((row: Competition) => (
+              competitions.map((row) => (
                 <TableRow key={row.name}>
                   <TableCell scope="row">
                     <Button color="primary" component={Link} to={`/editor/competition-id=${row.id}`}>
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index 19caef8f..abcfb399 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -5,10 +5,10 @@ import Drawer from '@material-ui/core/Drawer'
 import List from '@material-ui/core/List'
 import ListItemText from '@material-ui/core/ListItemText'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
-import React from 'react'
+import React, { useEffect } from 'react'
 import { useParams } from 'react-router-dom'
-import { useAppSelector } from '../../hooks'
-import { Slide } from '../../interfaces/Slide'
+import { getEditorCompetition } from '../../actions/editor'
+import { useAppDispatch, useAppSelector } from '../../hooks'
 import SettingsPanel from './components/SettingsPanel'
 import { SlideListItem, ToolBarContainer, ViewButton, ViewButtonGroup } from './styled'
 
@@ -63,7 +63,11 @@ interface CompetitionParams {
 const PresentationEditorPage: React.FC = () => {
   const classes = useStyles()
   const { id }: CompetitionParams = useParams()
-  const competition = useAppSelector((state) => state.competitions.competitions[+id - 1])
+  const dispatch = useAppDispatch()
+  const competition = useAppSelector((state) => state.editor.competition)
+  useEffect(() => {
+    dispatch(getEditorCompetition(id))
+  }, [])
   return (
     <div className={classes.root}>
       <CssBaseline />
@@ -96,7 +100,7 @@ const PresentationEditorPage: React.FC = () => {
         <div className={classes.toolbar} />
         <Divider />
         <List>
-          {competition.slides.map((slide: Slide) => (
+          {competition.slides.map((slide) => (
             <SlideListItem divider button key={slide.title}>
               <ListItemText primary={slide.title} />
             </SlideListItem>
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
index b668010b..04e1d74e 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -1,9 +1,9 @@
 import { Button, Divider, List, ListItem, ListItemText, TextField } from '@material-ui/core'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import CloseIcon from '@material-ui/icons/Close'
-import React, { useState } from 'react'
+import React, { useEffect, useState } from 'react'
 import { useParams } from 'react-router-dom'
-import { setCompetitionName } from '../../../actions/competitions'
+import { getEditorCompetition, setCompetitionName, submitCompetitionName } from '../../../actions/editor'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
 
 const useStyles = makeStyles((theme: Theme) =>
@@ -46,8 +46,10 @@ const CompetitionSettings: React.FC = () => {
   const { id }: CompetitionParams = useParams()
   const dispatch = useAppDispatch()
 
-  const competition = useAppSelector((state) => state.competitions.competitions[+id - 1])
-  const competitionName = competition.name
+  const competition = useAppSelector((state) => state.editor.competition)
+  useEffect(() => {
+    dispatch(getEditorCompetition(id))
+  }, [])
 
   const updateCompetitionName = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
     if (timerHandle) {
@@ -56,7 +58,10 @@ const CompetitionSettings: React.FC = () => {
     }
     //Only updates filter and api 100ms after last input was made
     setTimerHandle(window.setTimeout(() => dispatch(setCompetitionName(id, event.target.value)), 100))
-    dispatch(setCompetitionName(id, competitionName))
+    dispatch(setCompetitionName(id, competition.name))
+
+    setTimerHandle(window.setTimeout(() => dispatch(submitCompetitionName(id, event.target.value)), 100))
+    dispatch(submitCompetitionName(id, competition.name))
   }
 
   const initialList = [
@@ -77,7 +82,7 @@ const CompetitionSettings: React.FC = () => {
           className={classes.textInput}
           id="outlined-basic"
           label={'Tävlingsnamn'}
-          defaultValue={competitionName}
+          defaultValue={competition.name}
           onChange={updateCompetitionName}
           variant="outlined"
         />
diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts
index b12b5346..732cff39 100644
--- a/client/src/reducers/allReducers.ts
+++ b/client/src/reducers/allReducers.ts
@@ -3,6 +3,7 @@
 import { combineReducers } from 'redux'
 import citiesReducer from './citiesReducer'
 import competitionsReducer from './competitionsReducer'
+import editorReducer from './editorReducer'
 import uiReducer from './uiReducer'
 import userReducer from './userReducer'
 
@@ -12,5 +13,6 @@ const allReducers = combineReducers({
   UI: uiReducer,
   competitions: competitionsReducer,
   cities: citiesReducer,
+  editor: editorReducer,
 })
 export default allReducers
diff --git a/client/src/reducers/competitionsReducer.ts b/client/src/reducers/competitionsReducer.ts
index fabf4801..b3003d4a 100644
--- a/client/src/reducers/competitionsReducer.ts
+++ b/client/src/reducers/competitionsReducer.ts
@@ -39,18 +39,6 @@ export default function (state = initialState, action: AnyAction) {
         ...state,
         count: action.payload as number,
       }
-    case Types.SET_COMPETITION_NAME:
-      return {
-        ...state,
-        competitions: state.competitions.map((competition, i) =>
-          i === action.id
-            ? ({
-                ...competition,
-                name: action.name as string,
-              } as Competition)
-            : (competition as Competition)
-        ) as Competition[],
-      }
     default:
       return state
   }
diff --git a/client/src/reducers/editorReducer.ts b/client/src/reducers/editorReducer.ts
new file mode 100644
index 00000000..760e6007
--- /dev/null
+++ b/client/src/reducers/editorReducer.ts
@@ -0,0 +1,48 @@
+import { AnyAction } from 'redux'
+import Types from '../actions/types'
+import { Competition } from '../interfaces/Competition'
+
+interface EditorState {
+  competition: Competition
+}
+
+const initialState: EditorState = {
+  competition: {
+    name: '',
+    id: 0,
+    year: 0,
+    city: {
+      id: 0,
+      name: '',
+    },
+    slides: [],
+  },
+}
+
+export default function (state = initialState, action: AnyAction) {
+  switch (action.type) {
+    case Types.SET_EDITOR_COMPETITION:
+      return {
+        ...state,
+        competition: action.payload as Competition,
+      }
+    case Types.SET_COMPETITION_NAME:
+      return {
+        ...state,
+        competition: {
+          ...state.competition,
+          name: action.name,
+        } as Competition,
+      }
+    case Types.SET_COMPETITION_NAME:
+      return {
+        ...state,
+        competition: {
+          ...state.competition,
+          name: action.name,
+        } as Competition,
+      }
+    default:
+      return state
+  }
+}
-- 
GitLab


From 64ea931c9becc29563730ed01765f1982e7ed1c9 Mon Sep 17 00:00:00 2001
From: Sebastian Karlsson <sebka991@student.liu.se>
Date: Mon, 12 Apr 2021 16:26:41 +0200
Subject: [PATCH 06/21] Add database connection for slides and teams to editor,
 update api models.

---
 client/src/actions/editor.ts                  | 44 ++++++++++++++++++-
 client/src/interfaces/ApiModels.ts            | 20 +++++++--
 client/src/interfaces/ApiRichModels.ts        | 14 +++---
 .../components/CompetitionSettings.tsx        | 29 +++++-------
 client/src/reducers/editorReducer.ts          |  5 +--
 client/src/reducers/presentationReducer.ts    |  5 +--
 6 files changed, 80 insertions(+), 37 deletions(-)

diff --git a/client/src/actions/editor.ts b/client/src/actions/editor.ts
index cf00921a..dcbcdf23 100644
--- a/client/src/actions/editor.ts
+++ b/client/src/actions/editor.ts
@@ -8,7 +8,49 @@ export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch
     .then((res) => {
       dispatch({
         type: Types.SET_EDITOR_COMPETITION,
-        payload: res.data,
+        //res.data,
+        payload: {
+          name: 'Nåt hårdkodat',
+          id: 1,
+          year: 1337,
+          city_id: 1,
+          slides: [
+            {
+              competition_id: 1,
+              id: 1,
+              order: 1,
+              timer: 10,
+              title: 'Hej1',
+              questions: [],
+              body: 'Kropp',
+              settings: 'Inställning',
+            },
+            {
+              competition_id: 1,
+              id: 2,
+              order: 2,
+              timer: 15,
+              title: 'Hej2',
+              questions: [],
+              body: 'Kropp nr 2',
+              settings: 'Inställning 2',
+            },
+          ],
+          teams: [
+            {
+              id: 1,
+              name: 'Örkelljunga IK',
+              question_answers: [],
+              competition_id: 1,
+            },
+            {
+              id: 2,
+              name: 'Vadstena OK',
+              question_answers: [],
+              competition_id: 1,
+            },
+          ],
+        },
       })
     })
     .catch((err) => {
diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts
index 194fb2e7..149c2d16 100644
--- a/client/src/interfaces/ApiModels.ts
+++ b/client/src/interfaces/ApiModels.ts
@@ -2,10 +2,18 @@ interface NameID {
   id: number
   name: string
 }
-export interface City extends NameID {}
-export interface Role extends NameID {}
-export interface MediaType extends NameID {}
-export interface QuestionType extends NameID {}
+export interface City extends NameID {
+  users: User[]
+  competitions: Competition[]
+}
+
+export interface Role extends NameID {
+  users: User[]
+}
+
+export interface QuestionType extends NameID {
+  questions: Question[]
+}
 
 export interface Media {
   id: number
@@ -14,6 +22,10 @@ export interface Media {
   user_id: number
 }
 
+export interface MediaType extends NameID {
+  media: Media[]
+}
+
 export interface User extends NameID {
   email: string
   role_id: number
diff --git a/client/src/interfaces/ApiRichModels.ts b/client/src/interfaces/ApiRichModels.ts
index 3f7e0124..779dbd8a 100644
--- a/client/src/interfaces/ApiRichModels.ts
+++ b/client/src/interfaces/ApiRichModels.ts
@@ -1,10 +1,10 @@
-import { City, Component, Media, QuestionAnswer, QuestionType } from './ApiModels'
+import { QuestionAlternative, QuestionAnswer } from './ApiModels'
 
 export interface RichCompetition {
   name: string
   id: number
   year: number
-  city: City
+  city_id: number
   slides: RichSlide[]
   teams: RichTeam[]
 }
@@ -15,9 +15,9 @@ export interface RichSlide {
   timer: number
   title: string
   competition_id: number
-  question: RichQuestion[]
-  components: Component[]
-  medias: Media[]
+  questions: RichQuestion[]
+  body: string
+  settings: string
 }
 
 export interface RichTeam {
@@ -33,5 +33,7 @@ export interface RichQuestion {
   name: string
   title: string
   total_score: number
-  question_type: QuestionType
+  type_id: number
+  question_answers: QuestionAnswer[]
+  alternatives: QuestionAlternative[]
 }
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
index c60aba98..065d1151 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -13,7 +13,7 @@ import {
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import CloseIcon from '@material-ui/icons/Close'
 import axios from 'axios'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect } from 'react'
 import { useParams } from 'react-router-dom'
 import { getEditorCompetition } from '../../../actions/editor'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
@@ -78,38 +78,31 @@ const CompetitionSettings: React.FC = () => {
       .catch(console.log)
   }
 
-  const initialList = [
-    { id: '1', name: 'Lag1' },
-    { id: '2', name: 'Lag2' },
-    { id: '3', name: 'Lag3' },
-  ]
-  const handleClick = (id: string) => {
-    setTeams(teams.filter((item) => item.id !== id)) //Will not be done like this when api is used
+  const handleClick = async (tid: number) => {
+    await axios
+      .delete(`/competitions/${id}/teams/${tid}`, {})
+      .then(() => {
+        dispatch(getEditorCompetition(id))
+      })
+      .catch(console.log)
   }
-  const [teams, setTeams] = useState(initialList)
 
   const cities = useAppSelector((state) => state.cities.cities)
   const updateCompetitionCity = async (city: City) => {
-    console.log(city)
     await axios
-      .put(`/competitions/${id}`, { city: city })
+      .put(`/competitions/${id}`, { city_id: city.id })
       .then(() => {
         dispatch(getEditorCompetition(id))
       })
       .catch(console.log)
   }
   const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
-    updateCitySelected(event)
     cities.forEach((city) => {
       if (event.target.value === city.name) {
         updateCompetitionCity(city)
       }
     })
   }
-  const [citySelected, selectCity] = React.useState(competition.city.name)
-  const updateCitySelected = (event: React.ChangeEvent<{ value: unknown }>) => {
-    selectCity(event.target.value as string)
-  }
 
   return (
     <div className={classes.textInputContainer}>
@@ -125,7 +118,7 @@ const CompetitionSettings: React.FC = () => {
         <Divider />
         <FormControl variant="outlined" className={classes.dropDown}>
           <InputLabel id="region-selection-label">Region</InputLabel>
-          <Select value={citySelected} label="RegionSelect" onChange={handleChange}>
+          <Select value={cities[competition.city_id - 1]} label="RegionSelect" onChange={handleChange}>
             {cities.map((city) => (
               <MenuItem value={city.name} key={city.name}>
                 <Button>{city.name}</Button>
@@ -139,7 +132,7 @@ const CompetitionSettings: React.FC = () => {
         <ListItem>
           <ListItemText className={classes.textCenter} primary="Lag" />
         </ListItem>
-        {teams.map((team) => (
+        {competition.teams.map((team) => (
           <div key={team.id}>
             <ListItem divider button>
               <ListItemText primary={team.name} />
diff --git a/client/src/reducers/editorReducer.ts b/client/src/reducers/editorReducer.ts
index 08fc2ae0..606f5d69 100644
--- a/client/src/reducers/editorReducer.ts
+++ b/client/src/reducers/editorReducer.ts
@@ -11,10 +11,7 @@ const initialState: EditorState = {
     name: '',
     id: 0,
     year: 0,
-    city: {
-      id: 0,
-      name: '',
-    },
+    city_id: 1,
     slides: [],
     teams: [],
   },
diff --git a/client/src/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts
index d71bcafb..b4325680 100644
--- a/client/src/reducers/presentationReducer.ts
+++ b/client/src/reducers/presentationReducer.ts
@@ -14,10 +14,7 @@ const initialState: PresentationState = {
   competition: {
     name: '',
     id: 0,
-    city: {
-      id: 0,
-      name: '',
-    },
+    city_id: 0,
     slides: [],
     year: 0,
     teams: [],
-- 
GitLab


From 4e509e76d892dcd213f5ff5314176a036afd17d6 Mon Sep 17 00:00:00 2001
From: Emil <Emil>
Date: Mon, 12 Apr 2021 22:01:56 +0200
Subject: [PATCH 07/21] =?UTF-8?q?Skapat=20fullst=C3=A4ndig,=20h=C3=A5rdkod?=
 =?UTF-8?q?ad=20t=C3=A4vling.=20Visar=20data=20fr=C3=A5n=20denna=20i=20edi?=
 =?UTF-8?q?torn?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 client/src/actions/editor.ts                  | 132 ++++++++++++++++--
 client/src/interfaces/ApiModels.ts            |   2 +-
 .../components/CompetitionSettings.tsx        |   9 +-
 .../components/SlideSettings.tsx              |  99 ++++++++++---
 4 files changed, 205 insertions(+), 37 deletions(-)

diff --git a/client/src/actions/editor.ts b/client/src/actions/editor.ts
index dcbcdf23..d5e6fede 100644
--- a/client/src/actions/editor.ts
+++ b/client/src/actions/editor.ts
@@ -10,7 +10,7 @@ export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch
         type: Types.SET_EDITOR_COMPETITION,
         //res.data,
         payload: {
-          name: 'Nåt hårdkodat',
+          name: 'Tävling 1 (Hårdkodad)',
           id: 1,
           year: 1337,
           city_id: 1,
@@ -20,33 +20,143 @@ export const getEditorCompetition = (id: string) => async (dispatch: AppDispatch
               id: 1,
               order: 1,
               timer: 10,
-              title: 'Hej1',
-              questions: [],
-              body: 'Kropp',
-              settings: 'Inställning',
+              title: 'Sida 1',
+              questions: [
+                {
+                  id: 1,
+                  slide_id: 1,
+                  name: 'Fråga 1 namn',
+                  title: 'Fråga 1 titel',
+                  total_score: 5,
+                  type_id: 3,
+                  question_answers: [
+                    {
+                      id: 1,
+                      question_id: 1,
+                      team_id: 1,
+                      data: 'question answer data 1',
+                      score: 1,
+                    },
+                    {
+                      id: 2,
+                      question_id: 1,
+                      team_id: 2,
+                      data: 'question answer data 2',
+                      score: 3,
+                    },
+                  ],
+                  alternatives: [
+                    {
+                      id: 1,
+                      text: '1',
+                      value: true,
+                      question_id: 1,
+                    },
+                    {
+                      id: 2,
+                      text: '0',
+                      value: false,
+                      question_id: 1,
+                    },
+                  ],
+                },
+              ],
+              body: 'Slide body 1',
+              settings: 'Slide settings 1',
             },
+
             {
               competition_id: 1,
               id: 2,
               order: 2,
               timer: 15,
-              title: 'Hej2',
-              questions: [],
-              body: 'Kropp nr 2',
-              settings: 'Inställning 2',
+              title: 'Sida 2',
+              questions: [
+                {
+                  id: 2,
+                  slide_id: 2,
+                  name: 'Fråga 2 namn',
+                  title: 'Fråga 2 titel',
+                  total_score: 6,
+                  type_id: 3,
+                  question_answers: [
+                    {
+                      id: 3,
+                      question_id: 2,
+                      team_id: 1,
+                      data: 'question answer data 1',
+                      score: 1,
+                    },
+                    {
+                      id: 4,
+                      question_id: 2,
+                      team_id: 2,
+                      data: 'question answer data 2',
+                      score: 4,
+                    },
+                  ],
+                  alternatives: [
+                    {
+                      id: 1,
+                      text: '5',
+                      value: true,
+                      question_id: 2,
+                    },
+                    {
+                      id: 2,
+                      text: 'abc',
+                      value: false,
+                      question_id: 2,
+                    },
+                  ],
+                },
+              ],
+              body: 'Slide body 2',
+              settings: 'Slide settings 2',
             },
           ],
+
           teams: [
             {
               id: 1,
               name: 'Örkelljunga IK',
-              question_answers: [],
+              question_answers: [
+                {
+                  id: 1,
+                  question_id: 1,
+                  team_id: 1,
+                  data: 'question answer data 1',
+                  score: 1,
+                },
+                {
+                  id: 3,
+                  question_id: 2,
+                  team_id: 1,
+                  data: 'question answer data 1',
+                  score: 1,
+                },
+              ],
               competition_id: 1,
             },
             {
               id: 2,
               name: 'Vadstena OK',
-              question_answers: [],
+              question_answers: [
+                {
+                  id: 2,
+                  question_id: 1,
+                  team_id: 2,
+                  data: 'question answer data 2',
+                  score: 3,
+                },
+                {
+                  id: 4,
+                  question_id: 2,
+                  team_id: 2,
+                  data: 'question answer data 2',
+                  score: 4,
+                },
+              ],
               competition_id: 1,
             },
           ],
diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts
index 149c2d16..a154a0c8 100644
--- a/client/src/interfaces/ApiModels.ts
+++ b/client/src/interfaces/ApiModels.ts
@@ -57,7 +57,7 @@ export interface QuestionAlternative {
 export interface QuestionAnswer {
   id: number
   question_id: number
-  team_id: string
+  team_id: number
   data: string
   score: number
 }
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
index 065d1151..e8b0a4e0 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -15,6 +15,7 @@ import CloseIcon from '@material-ui/icons/Close'
 import axios from 'axios'
 import React, { useEffect } from 'react'
 import { useParams } from 'react-router-dom'
+import { getCities } from '../../../actions/cities'
 import { getEditorCompetition } from '../../../actions/editor'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
 import { City } from '../../../interfaces/ApiModels'
@@ -63,10 +64,10 @@ const CompetitionSettings: React.FC = () => {
   const classes = useStyles()
   const { id }: CompetitionParams = useParams()
   const dispatch = useAppDispatch()
-
   const competition = useAppSelector((state) => state.editor.competition)
   useEffect(() => {
     dispatch(getEditorCompetition(id))
+    dispatch(getCities())
   }, [])
 
   const updateCompetitionName = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
@@ -80,7 +81,7 @@ const CompetitionSettings: React.FC = () => {
 
   const handleClick = async (tid: number) => {
     await axios
-      .delete(`/competitions/${id}/teams/${tid}`, {})
+      .delete(`/competitions/${id}/teams/${tid}`)
       .then(() => {
         dispatch(getEditorCompetition(id))
       })
@@ -96,6 +97,7 @@ const CompetitionSettings: React.FC = () => {
       })
       .catch(console.log)
   }
+
   const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
     cities.forEach((city) => {
       if (event.target.value === city.name) {
@@ -118,7 +120,8 @@ const CompetitionSettings: React.FC = () => {
         <Divider />
         <FormControl variant="outlined" className={classes.dropDown}>
           <InputLabel id="region-selection-label">Region</InputLabel>
-          <Select value={cities[competition.city_id - 1]} label="RegionSelect" onChange={handleChange}>
+          {/*TODO: fixa så cities laddar in i statet likt i CompetitionManager*/}
+          <Select value={cities[competition.city_id - 1].name} label="RegionSelect" onChange={handleChange}>
             {cities.map((city) => (
               <MenuItem value={city.name} key={city.name}>
                 <Button>{city.name}</Button>
diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx
index 936f3d8b..5023cfcb 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx
@@ -10,10 +10,16 @@ import {
   Select,
   TextField,
 } from '@material-ui/core'
-import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
+import { CheckboxProps } from '@material-ui/core/Checkbox'
+import { green, grey } from '@material-ui/core/colors'
+import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles'
 import CloseIcon from '@material-ui/icons/Close'
 import MoreHorizOutlinedIcon from '@material-ui/icons/MoreHorizOutlined'
+import axios from 'axios'
 import React, { useState } from 'react'
+import { useParams } from 'react-router-dom'
+import { getEditorCompetition } from '../../../actions/editor'
+import { useAppDispatch, useAppSelector } from '../../../hooks'
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -58,22 +64,33 @@ const useStyles = makeStyles((theme: Theme) =>
   })
 )
 
+interface CompetitionParams {
+  id: string
+}
+
 const SlideSettings: React.FC = () => {
   const classes = useStyles()
-
-  const [slideTypeSelected, selectSlideType] = React.useState('')
-  const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
-    selectSlideType(event.target.value as string)
+  const { id }: CompetitionParams = useParams()
+  const dispatch = useAppDispatch()
+  const competition = useAppSelector((state) => state.editor.competition)
+  let currentSlide = competition.slides[0]
+  // Init currentSlide if slides are not in order
+  for (const slide of competition.slides) {
+    if (slide.order === 1) {
+      currentSlide = slide
+      break
+    }
   }
 
-  const answerList = [
-    { id: 'answer1', name: 'Svar 1' },
-    { id: 'answer2', name: 'Svar 2' },
-  ]
-  const handleCloseAnswerClick = (id: string) => {
-    setAnswers(answers.filter((item) => item.id !== id)) //Will not be done like this when api is used
+  const handleCloseAnswerClick = async (alternative: number) => {
+    await axios
+      // TODO: implementera API för att kunnata bort svarsalternativ
+      .delete(`/competitions/${id}/slide/question/alternative/${alternative}`)
+      .then(() => {
+        dispatch(getEditorCompetition(id))
+      })
+      .catch(console.log)
   }
-  const [answers, setAnswers] = useState(answerList)
 
   const textList = [
     { id: 'text1', name: 'Text 1' },
@@ -93,22 +110,53 @@ const SlideSettings: React.FC = () => {
   }
   const [pictures, setPictures] = useState(pictureList)
 
+  const updateSlideType = async (event: React.ChangeEvent<{ value: unknown }>) => {
+    await axios
+      // TODO: implementera API för att kunna ändra i questions->type_id
+      .put(`/competitions/${id}/slides/${currentSlide.id}`, { type_id: event.target.value })
+      .then(() => {
+        dispatch(getEditorCompetition(id))
+      })
+      .catch(console.log)
+  }
+
+  const updateAlternativeValue = async (event: React.ChangeEvent<HTMLInputElement>) => {
+    // Wheter the alternative is true or false
+    await axios
+      // TODO: implementera API för att kunna ändra i alternatives->value
+      .put(`/competitions/${id}/slides/${currentSlide.id}`, { value: event.target.value })
+      .then(() => {
+        dispatch(getEditorCompetition(id))
+      })
+      .catch(console.log)
+  }
+
+  const GreenCheckbox = withStyles({
+    root: {
+      color: grey[900],
+      '&$checked': {
+        color: green[600],
+      },
+    },
+    checked: {},
+  })((props: CheckboxProps) => <Checkbox color="default" {...props} />)
+
   return (
     <div className={classes.textInputContainer}>
       <div className={classes.whiteBackground}>
         <FormControl variant="outlined" className={classes.dropDown}>
           <InputLabel id="slide-type-selection-label">Sidtyp</InputLabel>
-          <Select value={slideTypeSelected} label="Sidtyp" defaultValue="informationSlide" onChange={handleChange}>
-            <MenuItem value="informationSlide">
+          <Select value={currentSlide.questions[0].type_id} label="Sidtyp" onChange={updateSlideType}>
+            <MenuItem value={0}>
               <Button>Informationssida</Button>
             </MenuItem>
-            <MenuItem value="textQuestion">
+            <MenuItem value={1}>
               <Button>Skriftlig fråga</Button>
             </MenuItem>
-            <MenuItem value="practicalQuestion">
+            <MenuItem value={2}>
               <Button>Praktisk fråga</Button>
             </MenuItem>
-            <MenuItem value="multipleChoiceQuestion">
+            <MenuItem value={3}>
               <Button>Flervalsfråga</Button>
             </MenuItem>
           </Select>
@@ -123,6 +171,7 @@ const SlideSettings: React.FC = () => {
           helperText="Lämna blank för att inte använda timerfunktionen"
           label="Timer"
           type="number"
+          value={currentSlide.timer}
         />
       </ListItem>
 
@@ -134,12 +183,18 @@ const SlideSettings: React.FC = () => {
             secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)"
           />
         </ListItem>
-        {answers.map((answer) => (
-          <div key={answer.id}>
+        {currentSlide.questions[0].alternatives.map((alt) => (
+          <div key={alt.id}>
             <ListItem divider>
-              <TextField className={classes.textInput} id="outlined-basic" label={answer.name} variant="outlined" />
-              <Checkbox color="default" />
-              <CloseIcon className={classes.clickableIcon} onClick={() => handleCloseAnswerClick(answer.id)} />
+              <TextField
+                className={classes.textInput}
+                id="outlined-basic"
+                label={`Svar ${alt.id}`}
+                value={alt.text}
+                variant="outlined"
+              />
+              <GreenCheckbox checked={alt.value} onChange={updateAlternativeValue} />
+              <CloseIcon className={classes.clickableIcon} onClick={() => handleCloseAnswerClick(alt.id)} />
             </ListItem>
           </div>
         ))}
-- 
GitLab


From a0e6de289863c430f8c083b149e2ecb3626e870a Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Tue, 13 Apr 2021 09:03:36 +0200
Subject: [PATCH 08/21] Add image input component

---
 .../presentationEditor/components/SlideSettings.tsx      | 9 +++++++--
 .../src/pages/presentationEditor/components/styled.tsx   | 4 ++++
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx
index 4b094021..f07105c8 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx
@@ -8,12 +8,13 @@ import {
   ListItemText,
   MenuItem,
   Select,
-  TextField,
+  TextField
 } from '@material-ui/core'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import CloseIcon from '@material-ui/icons/Close'
 import MoreHorizOutlinedIcon from '@material-ui/icons/MoreHorizOutlined'
 import React, { useState } from 'react'
+import { HiddenInput } from './styled'
 
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
@@ -184,7 +185,11 @@ const SlideSettings: React.FC = () => {
           </div>
         ))}
         <ListItem className={classes.center} button>
-          <Button>Lägg till bild</Button>
+          <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={console.log} />
+
+          <label htmlFor="contained-button-file">
+            <Button component="span">Lägg till bild</Button>
+          </label>
         </ListItem>
       </List>
 
diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx
index 62239c5b..c4584f87 100644
--- a/client/src/pages/presentationEditor/components/styled.tsx
+++ b/client/src/pages/presentationEditor/components/styled.tsx
@@ -13,3 +13,7 @@ export const SlideEditorContainer = styled.div`
   width: 100%;
   height: 100%;
 `
+
+export const HiddenInput = styled.input`
+  display: none;
+`
-- 
GitLab


From 2a957ce2a253691f96e231fe65ecac2cd1b45deb Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Tue, 13 Apr 2021 09:43:14 +0200
Subject: [PATCH 09/21] Add image selector

---
 .../components/SlideSettings.tsx              | 26 +++++++++++++++++--
 1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx
index 4b225676..d5ab4912 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx
@@ -132,6 +132,28 @@ const SlideSettings: React.FC = () => {
       .catch(console.log)
   }
 
+  const handleFileSelected = (e: React.ChangeEvent<HTMLInputElement>): void => {
+    if (e.target.files !== null && e.target.files[0]) {
+      const files = Array.from(e.target.files)
+      const file = files[0]
+      const reader = new FileReader()
+      reader.readAsDataURL(file)
+      reader.onload = function () {
+        console.log(reader.result)
+        // TODO: Send image to back-end (remove console.log)
+      }
+      reader.onerror = function (error) {
+        console.log('Error: ', error)
+      }
+    }
+  }
+
+  const handleAddText = async () => {
+    console.log('Add text component')
+    // TODO: post the new text]
+    setTexts([...texts, { id: 'newText', name: 'New Text' }])
+  }
+
   const GreenCheckbox = withStyles({
     root: {
       color: grey[900],
@@ -217,7 +239,7 @@ const SlideSettings: React.FC = () => {
             </ListItem>
           </div>
         ))}
-        <ListItem className={classes.center} button>
+        <ListItem className={classes.center} button onClick={handleAddText}>
           <Button>Lägg till text</Button>
         </ListItem>
       </List>
@@ -240,7 +262,7 @@ const SlideSettings: React.FC = () => {
           </div>
         ))}
         <ListItem className={classes.center} button>
-          <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={console.log} />
+          <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} />
 
           <label htmlFor="contained-button-file">
             <Button component="span">Lägg till bild</Button>
-- 
GitLab


From 393a76ebc20ea9aac1f77bb68fef50d5ecfe235f Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Wed, 14 Apr 2021 15:02:55 +0200
Subject: [PATCH 10/21] add: component

---
 server/app/apis/__init__.py           |  3 ++
 server/app/apis/components.py         | 30 +++++++++++
 server/app/core/dto.py                |  6 +++
 server/app/core/schemas.py            | 14 ++++++
 server/app/database/controller/add.py |  8 +++
 server/app/database/controller/get.py |  7 ++-
 server/app/database/models.py         | 72 +++++++++++++++++++++++++++
 7 files changed, 138 insertions(+), 2 deletions(-)

diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index 3b2ed213..9e3bd034 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -44,6 +44,7 @@ from flask_restx import Api
 
 from .auth import api as auth_ns
 from .competitions import api as comp_ns
+from .components import api as component_ns
 from .media import api as media_ns
 from .misc import api as misc_ns
 from .questions import api as question_ns
@@ -58,6 +59,8 @@ flask_api.add_namespace(user_ns, path="/api/users")
 flask_api.add_namespace(auth_ns, path="/api/auth")
 flask_api.add_namespace(comp_ns, path="/api/competitions")
 flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides")
+flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components/")
+
 flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams")
 flask_api.add_namespace(question_ns, path="/api/competitions/<CID>/questions")
 # flask_api.add_namespace(question_ns, path="/api/competitions/<CID>/slides/<SID>/question")
diff --git a/server/app/apis/components.py b/server/app/apis/components.py
index e69de29b..360463dc 100644
--- a/server/app/apis/components.py
+++ b/server/app/apis/components.py
@@ -0,0 +1,30 @@
+import app.database.controller as dbc
+from app.apis import admin_required, item_response, list_response
+from app.core.dto import ComponentDTO
+from app.core.parsers import competition_parser, competition_search_parser
+from app.database.models import Competition
+from flask.globals import request
+from flask_jwt_extended import jwt_required
+from flask_restx import Resource
+
+api = ComponentDTO.api
+schema = ComponentDTO.schema
+list_schema = ComponentDTO.list_schema
+
+
+@api.route("/<component_id>")
+@api.param("CID, SID, component_id")
+class ComponentByID(Resource):
+    @jwt_required
+    def get(self, CID, SID, component_id):
+        item = dbc.get.component(component_id)
+        return item_response(schema.dump(item))
+
+
+@api.route("/")
+@api.param("CID, SID")
+class ComponentList(Resource):
+    @jwt_required
+    def post(self, CID, SID):
+        item = dbc.add.component(**request.args)
+        return item_response(schema.dump(item))
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index 99d467ba..3e6b0cda 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -5,6 +5,12 @@ from flask_restx import Namespace, fields
 from flask_uploads import IMAGES, UploadSet
 
 
+class ComponentDTO:
+    api = Namespace("component")
+    schema = schemas.ComponentSchema(many=False)
+    list_schema = schemas.ComponentSchema(many=True)
+
+
 class MediaDTO:
     api = Namespace("media")
     image_set = UploadSet("photos", IMAGES)
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
index 27440642..fca727b2 100644
--- a/server/app/core/schemas.py
+++ b/server/app/core/schemas.py
@@ -113,3 +113,17 @@ class CompetitionSchema(BaseSchema):
     name = ma.auto_field()
     year = ma.auto_field()
     city_id = ma.auto_field()
+
+
+class ComponentSchema(BaseSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Component
+
+    id = ma.auto_field()
+    x = ma.auto_field()
+    y = ma.auto_field()
+    w = ma.auto_field()
+    h = ma.auto_field()
+    slide_id = ma.auto_field()
+    text = ma.auto_field()
+    image_id = ma.auto_field()
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index f612cdd6..8d17cdb6 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -4,6 +4,8 @@ from app.database.models import (
     Blacklist,
     City,
     Competition,
+    Component,
+    ImageComponent,
     Media,
     MediaType,
     Question,
@@ -11,6 +13,7 @@ from app.database.models import (
     Role,
     Slide,
     Team,
+    TextComponent,
     User,
 )
 from flask_restx import abort
@@ -31,6 +34,11 @@ def db_add(func):
     return wrapper
 
 
+@db_add
+def component(x, y, w, h, data, type_id, item_slide):
+    return Component(x, y, w, h, data, item_slide.id, type_id)
+
+
 @db_add
 def blacklist(jti):
     return Blacklist(jti)
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index a2b45fed..02e6df6a 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -1,11 +1,14 @@
-from app.database.models import Competition, Question, Slide, Team, User
-from sqlalchemy.sql.expression import outerjoin
+from app.database.models import Competition, Component, ImageComponent, Question, Slide, Team, TextComponent, User
 
 
 def user_exists(email):
     return User.query.filter(User.email == email).count() > 0
 
 
+def component(ID):
+    return Component.query.filter(Component.id == ID)
+
+
 def competition(CID, required=True, error_msg=None):
     return Competition.query.filter(Competition.id == CID).first_extended(required, error_msg)
 
diff --git a/server/app/database/models.py b/server/app/database/models.py
index 5a17fb0d..cc492222 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -130,6 +130,8 @@ class Slide(db.Model):
     background_image_id = db.Column(db.Integer, db.ForeignKey("media.id"), nullable=True)
     background_image = db.relationship("Media", uselist=False)
 
+    components = db.relationship("Component", backref="slide")
+
     def __init__(self, order, competition_id):
         self.order = order
         self.competition_id = competition_id
@@ -181,6 +183,76 @@ class QuestionAnswer(db.Model):
         self.team_id = team_id
 
 
+class Component(db.Model):
+    # __mapper_args__ = {"polymorphic_on": type, "polymorphic_identity": "component"}
+    id = db.Column(db.Integer, primary_key=True)
+    x = db.Column(db.Integer, nullable=False, default=0)
+    y = db.Column(db.Integer, nullable=False, default=0)
+    w = db.Column(db.Integer, nullable=False, default=1)
+    h = db.Column(db.Integer, nullable=False, default=1)
+    data = db.Column(db.Text)
+    type_id = db.Column(db.Integer, db.ForeignKey("component_type.id"), nullable=False)
+
+    slide_id = db.Column(db.Integer, db.ForeignKey("slide.id"), nullable=False)
+
+    def __init__(self, x, y, w, h, data, slide_id, type_id):
+        self.x = x
+        self.y = y
+        self.w = w
+        self.h = h
+        self.data = data
+        self.slide_id = slide_id
+        self.type_id = type_id
+
+
+"""
+class ImageComponent(Component):
+    __mapper_args__ = {"polymorphic_identity": "image_component"}
+
+    id = db.Column(db.Integer, db.ForeignKey("component.id"), primary_key=True)
+    # id = db.Column(db.Integer, primary_key=True)
+    image_id = db.Column(db.Integer, db.ForeignKey("media.id"), nullable=False)
+    image = db.relationship("Media", uselist=False)
+
+    def __init__(self, id, image_id, x, y, w, h, slide_id, type):
+        super.__init__(x, y, w, h, slide_id, type)
+        self.id = id
+        self.image_id = image_id
+
+
+class TextComponent(Component):
+    __mapper_args__ = {"polymorphic_identity": "text_component"}
+    id = db.Column(db.Integer, db.ForeignKey("component.id"), primary_key=True)
+    text = db.Column(db.Text, default="", nullable=False)
+
+    def __init__(self, id, text, x, y, w, h, slide_id, type):
+        super.__init__(x, y, w, h, slide_id, type)
+        self.id = id
+        self.text = text
+"""
+
+
+class Code(db.Model):
+    table_args = (db.UniqueConstraint("pointer", "type"),)
+    id = db.Column(db.Integer, primary_key=True)
+    code = db.Column(db.Text, unique=True)
+    pointer = db.Column(db.Integer, nullable=False)
+
+    view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False)
+
+
+class ViewType(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    name = db.Column(db.Text)
+    codes = db.relationship("Code", backref="view_type")
+
+
+class ComponentType(db.Model):
+    id = db.Column(db.Integer, primary_key=True)
+    name = db.Column(db.Text)
+    components = db.relationship("Component", backref="component_type")
+
+
 class MediaType(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(STRING_SIZE), unique=True)
-- 
GitLab


From 039cfb63743e587f2ed68fd716814a813be64a1b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se>
Date: Wed, 14 Apr 2021 15:41:21 +0200
Subject: [PATCH 11/21] Add get component list, get by id and add api

---
 server/app/apis/components.py         | 11 +++++++++--
 server/app/core/parsers.py            | 10 ++++++++++
 server/app/core/schemas.py            |  4 ++--
 server/app/database/controller/add.py |  2 --
 server/app/database/controller/get.py | 11 ++++++++---
 server/app/database/models.py         |  5 ++---
 6 files changed, 31 insertions(+), 12 deletions(-)

diff --git a/server/app/apis/components.py b/server/app/apis/components.py
index 360463dc..73e2a907 100644
--- a/server/app/apis/components.py
+++ b/server/app/apis/components.py
@@ -1,7 +1,7 @@
 import app.database.controller as dbc
 from app.apis import admin_required, item_response, list_response
 from app.core.dto import ComponentDTO
-from app.core.parsers import competition_parser, competition_search_parser
+from app.core.parsers import component_parser
 from app.database.models import Competition
 from flask.globals import request
 from flask_jwt_extended import jwt_required
@@ -24,7 +24,14 @@ class ComponentByID(Resource):
 @api.route("/")
 @api.param("CID, SID")
 class ComponentList(Resource):
+    @jwt_required
+    def get(self, CID, SID):
+        items = dbc.get.component_list(SID)
+        return list_response(list_schema.dump(items))
+
     @jwt_required
     def post(self, CID, SID):
-        item = dbc.add.component(**request.args)
+        args = component_parser.parse_args()
+        item_slide = dbc.get.slide(CID, SID)
+        item = dbc.add.component(item_slide=item_slide, **args)
         return item_response(schema.dump(item))
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
index 7a1c6089..a8136c9c 100644
--- a/server/app/core/parsers.py
+++ b/server/app/core/parsers.py
@@ -68,3 +68,13 @@ team_parser.add_argument("name", type=str, location="json")
 ###SEARCH_COMPETITION####
 media_parser_search = search_parser.copy()
 media_parser_search.add_argument("filename", type=str, default=None, location="args")
+
+
+###COMPONENT###
+component_parser = reqparse.RequestParser()
+component_parser.add_argument("x", type=str, default=None, location="json")
+component_parser.add_argument("y", type=int, default=None, location="json")
+component_parser.add_argument("w", type=int, default=None, location="json")
+component_parser.add_argument("h", type=int, default=None, location="json")
+component_parser.add_argument("data", type=dict, default=None, location="json")
+component_parser.add_argument("type_id", type=int, default=None, location="json")
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
index fca727b2..2cd14a31 100644
--- a/server/app/core/schemas.py
+++ b/server/app/core/schemas.py
@@ -124,6 +124,6 @@ class ComponentSchema(BaseSchema):
     y = ma.auto_field()
     w = ma.auto_field()
     h = ma.auto_field()
+    data = ma.auto_field()  # TODO: Convert this to dict, or save as dict to begin with
     slide_id = ma.auto_field()
-    text = ma.auto_field()
-    image_id = ma.auto_field()
+    type_id = ma.auto_field()
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 8d17cdb6..162bbd8c 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -5,7 +5,6 @@ from app.database.models import (
     City,
     Competition,
     Component,
-    ImageComponent,
     Media,
     MediaType,
     Question,
@@ -13,7 +12,6 @@ from app.database.models import (
     Role,
     Slide,
     Team,
-    TextComponent,
     User,
 )
 from flask_restx import abort
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index 02e6df6a..fa6914b2 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -1,12 +1,12 @@
-from app.database.models import Competition, Component, ImageComponent, Question, Slide, Team, TextComponent, User
+from app.database.models import Competition, Component, Question, Slide, Team, User
 
 
 def user_exists(email):
     return User.query.filter(User.email == email).count() > 0
 
 
-def component(ID):
-    return Component.query.filter(Component.id == ID)
+def component(ID, required=True, error_msg=None):
+    return Component.query.filter(Component.id == ID).first_extended(required, error_msg)
 
 
 def competition(CID, required=True, error_msg=None):
@@ -55,5 +55,10 @@ def slide_list(CID):
     return Slide.query.filter(Slide.competition_id == CID).all()
 
 
+def component_list(SID):
+    # TODO: Maybe take CID as argument and make sure that SID is in that competition?
+    return Component.query.filter(Component.slide_id == SID).all()
+
+
 def slide_count(CID):
     return Slide.query.filter(Slide.competition_id == CID).count()
diff --git a/server/app/database/models.py b/server/app/database/models.py
index cc492222..ec29860f 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -190,9 +190,8 @@ class Component(db.Model):
     y = db.Column(db.Integer, nullable=False, default=0)
     w = db.Column(db.Integer, nullable=False, default=1)
     h = db.Column(db.Integer, nullable=False, default=1)
-    data = db.Column(db.Text)
+    data = db.Column(db.Text)  # TODO: Don't save this as text
     type_id = db.Column(db.Integer, db.ForeignKey("component_type.id"), nullable=False)
-
     slide_id = db.Column(db.Integer, db.ForeignKey("slide.id"), nullable=False)
 
     def __init__(self, x, y, w, h, data, slide_id, type_id):
@@ -200,7 +199,7 @@ class Component(db.Model):
         self.y = y
         self.w = w
         self.h = h
-        self.data = data
+        self.data = str(data)
         self.slide_id = slide_id
         self.type_id = type_id
 
-- 
GitLab


From 819d443d05a7ee10c6b026fe894c15d7dac4c47f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se>
Date: Wed, 14 Apr 2021 16:28:42 +0200
Subject: [PATCH 12/21] Make data dict type

---
 server/app/core/schemas.py            |  2 +-
 server/app/database/controller/add.py |  2 +-
 server/app/database/models.py         | 25 ++++++++++++++++++++++---
 3 files changed, 24 insertions(+), 5 deletions(-)

diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
index 2cd14a31..058efed7 100644
--- a/server/app/core/schemas.py
+++ b/server/app/core/schemas.py
@@ -124,6 +124,6 @@ class ComponentSchema(BaseSchema):
     y = ma.auto_field()
     w = ma.auto_field()
     h = ma.auto_field()
-    data = ma.auto_field()  # TODO: Convert this to dict, or save as dict to begin with
+    data = ma.auto_field()  # TODO: Convert this to dict
     slide_id = ma.auto_field()
     type_id = ma.auto_field()
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 162bbd8c..9e2a8e11 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -33,7 +33,7 @@ def db_add(func):
 
 
 @db_add
-def component(x, y, w, h, data, type_id, item_slide):
+def component(x, y, w, h, data, item_slide, type_id):
     return Component(x, y, w, h, data, item_slide.id, type_id)
 
 
diff --git a/server/app/database/models.py b/server/app/database/models.py
index ec29860f..29cbe3b5 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -1,6 +1,9 @@
+import json
+
 from app.core import bcrypt, db
 from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
 from sqlalchemy.orm import backref
+from sqlalchemy.types import TypeDecorator
 
 STRING_SIZE = 254
 
@@ -183,6 +186,22 @@ class QuestionAnswer(db.Model):
         self.team_id = team_id
 
 
+class Dictionary(TypeDecorator):
+
+    impl = db.Text(1024)
+
+    def process_bind_param(self, value, dialect):
+        if value is not None:
+            value = json.dumps(value).replace("'", '"')
+
+        return value
+
+    def process_result_value(self, value, dialect):
+        if value is not None:
+            value = json.loads(value)
+        return value
+
+
 class Component(db.Model):
     # __mapper_args__ = {"polymorphic_on": type, "polymorphic_identity": "component"}
     id = db.Column(db.Integer, primary_key=True)
@@ -190,16 +209,16 @@ class Component(db.Model):
     y = db.Column(db.Integer, nullable=False, default=0)
     w = db.Column(db.Integer, nullable=False, default=1)
     h = db.Column(db.Integer, nullable=False, default=1)
-    data = db.Column(db.Text)  # TODO: Don't save this as text
-    type_id = db.Column(db.Integer, db.ForeignKey("component_type.id"), nullable=False)
+    data = db.Column(Dictionary())
     slide_id = db.Column(db.Integer, db.ForeignKey("slide.id"), nullable=False)
+    type_id = db.Column(db.Integer, db.ForeignKey("component_type.id"), nullable=False)
 
     def __init__(self, x, y, w, h, data, slide_id, type_id):
         self.x = x
         self.y = y
         self.w = w
         self.h = h
-        self.data = str(data)
+        self.data = data
         self.slide_id = slide_id
         self.type_id = type_id
 
-- 
GitLab


From 8db228bf8fe8e3bbd5c5adcc00add9e8a611b268 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Fri, 16 Apr 2021 19:23:59 +0200
Subject: [PATCH 13/21] fix: component and you now get all types by get
 misc/types

---
 server/app/apis/components.py            | 17 +++++++-
 server/app/apis/misc.py                  | 39 +++++++++--------
 server/app/core/dto.py                   |  3 ++
 server/app/core/parsers.py               |  5 ++-
 server/app/core/rich_schemas.py          |  1 +
 server/app/core/schemas.py               | 34 +++++++++------
 server/app/database/controller/add.py    | 16 ++++++-
 server/app/database/controller/delete.py |  4 ++
 server/app/database/controller/edit.py   | 17 ++++++++
 server/app/database/controller/get.py    | 23 +++++++++-
 server/app/database/models.py            | 45 +++++++-------------
 server/configmodule.py                   |  3 +-
 server/populate.py                       | 25 +++++++----
 server/tests/test_app.py                 | 53 ++++++++++--------------
 server/tests/test_helpers.py             | 30 ++++++++------
 15 files changed, 194 insertions(+), 121 deletions(-)

diff --git a/server/app/apis/components.py b/server/app/apis/components.py
index 73e2a907..da211895 100644
--- a/server/app/apis/components.py
+++ b/server/app/apis/components.py
@@ -1,7 +1,8 @@
+import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import admin_required, item_response, list_response
 from app.core.dto import ComponentDTO
-from app.core.parsers import component_parser
+from app.core.parsers import component_create_parser, component_parser
 from app.database.models import Competition
 from flask.globals import request
 from flask_jwt_extended import jwt_required
@@ -20,6 +21,18 @@ class ComponentByID(Resource):
         item = dbc.get.component(component_id)
         return item_response(schema.dump(item))
 
+    @jwt_required
+    def put(self, CID, SID, component_id):
+        args = component_parser.parse_args()
+        item = dbc.edit.component(**args)
+        return item_response(schema.dump(item))
+
+    @jwt_required
+    def delete(self, CID, SID, component_id):
+        item = dbc.get.component(component_id)
+        dbc.delete.component(item)
+        return {}, codes.NO_CONTENT
+
 
 @api.route("/")
 @api.param("CID, SID")
@@ -31,7 +44,7 @@ class ComponentList(Resource):
 
     @jwt_required
     def post(self, CID, SID):
-        args = component_parser.parse_args()
+        args = component_create_parser.parse_args()
         item_slide = dbc.get.slide(CID, SID)
         item = dbc.add.component(item_slide=item_slide, **args)
         return item_response(schema.dump(item))
diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py
index 7fbb222d..b40f97a8 100644
--- a/server/app/apis/misc.py
+++ b/server/app/apis/misc.py
@@ -1,7 +1,7 @@
 import app.database.controller as dbc
 from app.apis import admin_required, item_response, list_response
 from app.core.dto import MiscDTO
-from app.database.models import City, MediaType, QuestionType, Role
+from app.database.models import City, ComponentType, MediaType, QuestionType, Role, ViewType
 from flask_jwt_extended import jwt_required
 from flask_restx import Resource, reqparse
 
@@ -9,6 +9,9 @@ api = MiscDTO.api
 
 question_type_schema = MiscDTO.question_type_schema
 media_type_schema = MiscDTO.media_type_schema
+component_type_schema = MiscDTO.component_type_schema
+view_type_schema = MiscDTO.view_type_schema
+
 role_schema = MiscDTO.role_schema
 city_schema = MiscDTO.city_schema
 
@@ -17,27 +20,23 @@ name_parser = reqparse.RequestParser()
 name_parser.add_argument("name", type=str, required=True, location="json")
 
 
-@api.route("/media_types")
-class MediaTypeList(Resource):
-    @jwt_required
-    def get(self):
-        items = MediaType.query.all()
-        return list_response(media_type_schema.dump(items))
-
-
-@api.route("/question_types")
-class QuestionTypeList(Resource):
+@api.route("/types")
+class TypesList(Resource):
     @jwt_required
     def get(self):
-        items = QuestionType.query.all()
-        return list_response(question_type_schema.dump(items))
+        result = {}
+        result["media_types"] = media_type_schema.dump(dbc.get.all(MediaType))
+        result["component_types"] = component_type_schema.dump(dbc.get.all(ComponentType))
+        result["question_types"] = question_type_schema.dump(dbc.get.all(QuestionType))
+        result["view_types"] = view_type_schema.dump(dbc.get.all(ViewType))
+        return result
 
 
 @api.route("/roles")
 class RoleList(Resource):
     @jwt_required
     def get(self):
-        items = Role.query.all()
+        items = dbc.get.all(Role)
         return list_response(role_schema.dump(items))
 
 
@@ -45,14 +44,14 @@ class RoleList(Resource):
 class CitiesList(Resource):
     @jwt_required
     def get(self):
-        items = City.query.all()
+        items = dbc.get.all(City)
         return list_response(city_schema.dump(items))
 
     @jwt_required
     def post(self):
         args = name_parser.parse_args(strict=True)
         dbc.add.city(args["name"])
-        items = City.query.all()
+        items = dbc.get.all(City)
         return list_response(city_schema.dump(items))
 
 
@@ -61,16 +60,16 @@ class CitiesList(Resource):
 class Cities(Resource):
     @jwt_required
     def put(self, ID):
-        item = City.query.filter(City.id == ID).first()
+        item = dbc.get.one(City, ID)
         args = name_parser.parse_args(strict=True)
         item.name = args["name"]
         dbc.commit_and_refresh(item)
-        items = City.query.all()
+        items = dbc.get.all(City)
         return list_response(city_schema.dump(items))
 
     @jwt_required
     def delete(self, ID):
-        item = City.query.filter(City.id == ID).first()
+        item = dbc.get.one(City, ID)
         dbc.delete.default(item)
-        items = City.query.all()
+        items = dbc.get.all(City)
         return list_response(city_schema.dump(items))
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index 3e6b0cda..a6899fc7 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -3,6 +3,7 @@ import app.core.schemas as schemas
 import marshmallow as ma
 from flask_restx import Namespace, fields
 from flask_uploads import IMAGES, UploadSet
+from sqlalchemy.sql.expression import true
 
 
 class ComponentDTO:
@@ -53,6 +54,8 @@ class MiscDTO:
     role_schema = schemas.RoleSchema(many=True)
     question_type_schema = schemas.QuestionTypeSchema(many=True)
     media_type_schema = schemas.MediaTypeSchema(many=True)
+    component_type_schema = schemas.ComponentTypeSchema(many=True)
+    view_type_schema = schemas.ViewTypeSchema(many=True)
     city_schema = schemas.CitySchema(many=True)
 
 
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
index a8136c9c..151924ca 100644
--- a/server/app/core/parsers.py
+++ b/server/app/core/parsers.py
@@ -77,4 +77,7 @@ component_parser.add_argument("y", type=int, default=None, location="json")
 component_parser.add_argument("w", type=int, default=None, location="json")
 component_parser.add_argument("h", type=int, default=None, location="json")
 component_parser.add_argument("data", type=dict, default=None, location="json")
-component_parser.add_argument("type_id", type=int, default=None, location="json")
+
+component_create_parser = component_parser.copy()
+component_create_parser.replace_argument("data", type=dict, required=True, location="json")
+component_create_parser.add_argument("type_id", type=int, required=True, location="json")
diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py
index ab1b3abb..8c220e78 100644
--- a/server/app/core/rich_schemas.py
+++ b/server/app/core/rich_schemas.py
@@ -53,6 +53,7 @@ class SlideSchemaRich(RichSchema):
     timer = ma.auto_field()
     competition_id = ma.auto_field()
     questions = fields.Nested(QuestionSchemaRich, many=True)
+    components = fields.Nested(schemas.ComponentSchema, many=True)
 
 
 class CompetitionSchemaRich(RichSchema):
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
index 058efed7..ee909e4b 100644
--- a/server/app/core/schemas.py
+++ b/server/app/core/schemas.py
@@ -10,12 +10,30 @@ class BaseSchema(ma.SQLAlchemySchema):
         include_relationships = False
 
 
-class QuestionTypeSchema(BaseSchema):
+class IdNameSchema(BaseSchema):
+
+    id = fields.fields.Integer()
+    name = fields.fields.String()
+
+
+class QuestionTypeSchema(IdNameSchema):
     class Meta(BaseSchema.Meta):
         model = models.QuestionType
 
-    id = ma.auto_field()
-    name = ma.auto_field()
+
+class MediaTypeSchema(IdNameSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.MediaType
+
+
+class ComponentTypeSchema(IdNameSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.ComponentType
+
+
+class ViewTypeSchema(IdNameSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.ViewType
 
 
 class QuestionSchema(BaseSchema):
@@ -40,14 +58,6 @@ class QuestionAnswerSchema(BaseSchema):
     team_id = ma.auto_field()
 
 
-class MediaTypeSchema(BaseSchema):
-    class Meta(BaseSchema.Meta):
-        model = models.MediaType
-
-    id = ma.auto_field()
-    name = ma.auto_field()
-
-
 class RoleSchema(BaseSchema):
     class Meta(BaseSchema.Meta):
         model = models.Role
@@ -124,6 +134,6 @@ class ComponentSchema(BaseSchema):
     y = ma.auto_field()
     w = ma.auto_field()
     h = ma.auto_field()
-    data = ma.auto_field()  # TODO: Convert this to dict
+    data = ma.Function(lambda obj: obj.data)
     slide_id = ma.auto_field()
     type_id = ma.auto_field()
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 9e2a8e11..0e955a9e 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -5,6 +5,7 @@ from app.database.models import (
     City,
     Competition,
     Component,
+    ComponentType,
     Media,
     MediaType,
     Question,
@@ -13,6 +14,7 @@ from app.database.models import (
     Slide,
     Team,
     User,
+    ViewType,
 )
 from flask_restx import abort
 
@@ -33,8 +35,8 @@ def db_add(func):
 
 
 @db_add
-def component(x, y, w, h, data, item_slide, type_id):
-    return Component(x, y, w, h, data, item_slide.id, type_id)
+def component(type_id, item_slide, data, x=0, y=0, w=0, h=0):
+    return Component(item_slide.id, type_id, data, x, y, w, h)
 
 
 @db_add
@@ -83,6 +85,16 @@ def questionType(name):
     return QuestionType(name)
 
 
+@db_add
+def componentType(name):
+    return ComponentType(name)
+
+
+@db_add
+def viewType(name):
+    return ViewType(name)
+
+
 @db_add
 def role(name):
     return Role(name)
diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py
index 65527ee9..2eb040c9 100644
--- a/server/app/database/controller/delete.py
+++ b/server/app/database/controller/delete.py
@@ -8,6 +8,10 @@ def default(item):
     db.session.commit()
 
 
+def component(item_component):
+    default(item_component)
+
+
 def slide(item_slide):
     for item_question in item_slide.questions:
         question(item_question)
diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py
index 0a1b190f..3afcb4d4 100644
--- a/server/app/database/controller/edit.py
+++ b/server/app/database/controller/edit.py
@@ -20,6 +20,23 @@ def switch_order(item1, item2):
     return item1
 
 
+def component(item, x, y, w, h, data):
+    if x:
+        item.x = x
+    if y:
+        item.y = y
+    if w:
+        item.w = w
+    if h:
+        item.h = h
+    if data:
+        item.data = data
+
+    db.session.commit()
+    db.session.refresh(item)
+    return item
+
+
 def slide(item, title=None, timer=None):
     if title:
         item.title = title
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index fa6914b2..892d251b 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -1,4 +1,25 @@
-from app.database.models import Competition, Component, Question, Slide, Team, User
+from app.core import db
+from app.database.models import (
+    City,
+    Competition,
+    Component,
+    ComponentType,
+    MediaType,
+    Question,
+    QuestionType,
+    Role,
+    Slide,
+    Team,
+    User,
+)
+
+
+def all(db_type):
+    return db_type.query.all()
+
+
+def one(db_type, id, required=True, error_msg=None):
+    return db_type.query.filter(db_type.id == id).first_extended(required, error_msg)
 
 
 def user_exists(email):
diff --git a/server/app/database/models.py b/server/app/database/models.py
index 29cbe3b5..6396e035 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -203,7 +203,6 @@ class Dictionary(TypeDecorator):
 
 
 class Component(db.Model):
-    # __mapper_args__ = {"polymorphic_on": type, "polymorphic_identity": "component"}
     id = db.Column(db.Integer, primary_key=True)
     x = db.Column(db.Integer, nullable=False, default=0)
     y = db.Column(db.Integer, nullable=False, default=0)
@@ -213,7 +212,7 @@ class Component(db.Model):
     slide_id = db.Column(db.Integer, db.ForeignKey("slide.id"), nullable=False)
     type_id = db.Column(db.Integer, db.ForeignKey("component_type.id"), nullable=False)
 
-    def __init__(self, x, y, w, h, data, slide_id, type_id):
+    def __init__(self, slide_id, type_id, data, x=0, y=0, w=1, h=1):
         self.x = x
         self.y = y
         self.w = w
@@ -223,33 +222,6 @@ class Component(db.Model):
         self.type_id = type_id
 
 
-"""
-class ImageComponent(Component):
-    __mapper_args__ = {"polymorphic_identity": "image_component"}
-
-    id = db.Column(db.Integer, db.ForeignKey("component.id"), primary_key=True)
-    # id = db.Column(db.Integer, primary_key=True)
-    image_id = db.Column(db.Integer, db.ForeignKey("media.id"), nullable=False)
-    image = db.relationship("Media", uselist=False)
-
-    def __init__(self, id, image_id, x, y, w, h, slide_id, type):
-        super.__init__(x, y, w, h, slide_id, type)
-        self.id = id
-        self.image_id = image_id
-
-
-class TextComponent(Component):
-    __mapper_args__ = {"polymorphic_identity": "text_component"}
-    id = db.Column(db.Integer, db.ForeignKey("component.id"), primary_key=True)
-    text = db.Column(db.Text, default="", nullable=False)
-
-    def __init__(self, id, text, x, y, w, h, slide_id, type):
-        super.__init__(x, y, w, h, slide_id, type)
-        self.id = id
-        self.text = text
-"""
-
-
 class Code(db.Model):
     table_args = (db.UniqueConstraint("pointer", "type"),)
     id = db.Column(db.Integer, primary_key=True)
@@ -258,18 +230,29 @@ class Code(db.Model):
 
     view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False)
 
+    def __init__(self, code, pointer, view_type_id):
+        self.code = code
+        self.pointer = pointer
+        self.view_type_id = view_type_id
+
 
 class ViewType(db.Model):
     id = db.Column(db.Integer, primary_key=True)
-    name = db.Column(db.Text)
+    name = db.Column(db.String(STRING_SIZE), unique=True)
     codes = db.relationship("Code", backref="view_type")
 
+    def __init__(self, name):
+        self.name = name
+
 
 class ComponentType(db.Model):
     id = db.Column(db.Integer, primary_key=True)
-    name = db.Column(db.Text)
+    name = db.Column(db.String(STRING_SIZE), unique=True)
     components = db.relationship("Component", backref="component_type")
 
+    def __init__(self, name):
+        self.name = name
+
 
 class MediaType(db.Model):
     id = db.Column(db.Integer, primary_key=True)
diff --git a/server/configmodule.py b/server/configmodule.py
index fcf23cbf..2d525424 100644
--- a/server/configmodule.py
+++ b/server/configmodule.py
@@ -15,12 +15,13 @@ class Config:
     JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30)
     UPLOADED_PHOTOS_DEST = "static/images"  # os.getcwd()
     SECRET_KEY = os.urandom(24)
+    SQLALCHEMY_ECHO = False
 
 
 class DevelopmentConfig(Config):
     DEBUG = True
     SQLALCHEMY_DATABASE_URI = "sqlite:///database.db"
-    SQLALCHEMY_ECHO = True
+    SQLALCHEMY_ECHO = False
 
 
 class TestingConfig(Config):
diff --git a/server/populate.py b/server/populate.py
index e2ca4776..45eb6005 100644
--- a/server/populate.py
+++ b/server/populate.py
@@ -8,21 +8,30 @@ from app.database.models import City, Competition, MediaType, QuestionType, Role
 def _add_items():
     media_types = ["Image", "Video"]
     question_types = ["Boolean", "Multiple", "Text"]
+    component_types = ["Text", "Image"]
+    view_types = ["Team", "Judge", "Audience"]
+
     roles = ["Admin", "Editor"]
     cities = ["Linköping", "Stockholm", "Norrköping", "Örkelljunga"]
     teams = ["Gymnasieskola A", "Gymnasieskola B", "Gymnasieskola C"]
 
-    for team_name in media_types:
-        dbc.add.mediaType(team_name)
+    for name in media_types:
+        dbc.add.mediaType(name)
+
+    for name in question_types:
+        dbc.add.questionType(name)
+
+    for name in component_types:
+        dbc.add.componentType(name)
 
-    for team_name in question_types:
-        dbc.add.questionType(team_name)
+    for name in view_types:
+        dbc.add.viewType(name)
 
-    for team_name in roles:
-        dbc.add.role(team_name)
+    for name in roles:
+        dbc.add.role(name)
 
-    for team_name in cities:
-        dbc.add.city(team_name)
+    for name in cities:
+        dbc.add.city(name)
 
     admin_id = Role.query.filter(Role.name == "Admin").one().id
     editor_id = Role.query.filter(Role.name == "Editor").one().id
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 1cbacc33..67dbac72 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -2,8 +2,7 @@ import app.core.http_codes as codes
 from app.database.models import Slide
 
 from tests import app, client, db
-from tests.test_helpers import (add_default_values, change_order_test, delete,
-                                get, post, put)
+from tests.test_helpers import add_default_values, change_order_test, delete, get, post, put
 
 
 def test_misc_api(client):
@@ -14,6 +13,14 @@ def test_misc_api(client):
     assert response.status_code == codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
+    # Get types
+    response, body = get(client, "/api/misc/types", headers=headers)
+    assert response.status_code == codes.OK
+    assert len(body["media_types"]) >= 2
+    assert len(body["question_types"]) >= 3
+    assert len(body["component_types"]) >= 2
+    assert len(body["view_types"]) >= 2
+
     ## Get misc
     response, body = get(client, "/api/misc/roles", headers=headers)
     assert response.status_code == codes.OK
@@ -21,44 +28,33 @@ def test_misc_api(client):
 
     response, body = get(client, "/api/misc/cities", headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] == 2
-    assert body["items"][0]["name"] == "Linköping"
-    assert body["items"][1]["name"] == "Testköping"
-
-    response, body = get(client, "/api/misc/media_types", headers=headers)
-    assert response.status_code == codes.OK
     assert body["count"] >= 2
-
-    response, body = get(client, "/api/misc/question_types", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] >= 3
+    assert body["items"][0]["name"] == "Linköping" and body["items"][1]["name"] == "Testköping"
 
     ## Cities
     response, body = post(client, "/api/misc/cities", {"name": "Göteborg"}, headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] >= 2
-    assert body["items"][2]["name"] == "Göteborg"
+    assert body["count"] >= 2 and body["items"][2]["name"] == "Göteborg"
 
     # Rename city
     response, body = put(client, "/api/misc/cities/3", {"name": "Gbg"}, headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] >= 2
-    assert body["items"][2]["name"] == "Gbg"
+    assert body["count"] >= 2 and body["items"][2]["name"] == "Gbg"
 
     # Delete city
     # First checks current cities
     response, body = get(client, "/api/misc/cities", headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] == 3
+    assert body["count"] >= 3
     assert body["items"][0]["name"] == "Linköping"
     assert body["items"][1]["name"] == "Testköping"
     assert body["items"][2]["name"] == "Gbg"
+
     # Deletes city
     response, body = delete(client, "/api/misc/cities/3", headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] == 2
-    assert body["items"][0]["name"] == "Linköping"
-    assert body["items"][1]["name"] == "Testköping"
+    assert body["count"] >= 2
+    assert body["items"][0]["name"] == "Linköping" and body["items"][1]["name"] == "Testköping"
 
 
 def test_competition_api(client):
@@ -152,8 +148,7 @@ def test_auth_and_user_api(client):
     response, body = put(client, "/api/users", {"name": "carl carlsson", "city_id": 2, "role_id": 1}, headers=headers)
     assert response.status_code == codes.OK
     assert body["name"] == "Carl Carlsson"
-    assert body["city"]["id"] == 2
-    assert body["role"]["id"] == 1
+    assert body["city"]["id"] == 2 and body["role"]["id"] == 1
 
     # Find other user
     response, body = get(
@@ -244,16 +239,14 @@ def test_slide_api(client):
 
     # Get slides
     CID = 2
-    num_slides = 3
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] == num_slides
+    assert body["count"] == 3
 
     # Add slide
     response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers)
-    num_slides += 1
     assert response.status_code == codes.OK
-    assert body["count"] == num_slides
+    assert body["count"] == 4
 
     # Get slide
     SID = 1
@@ -286,21 +279,19 @@ def test_slide_api(client):
 
     # Delete slide
     response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers)
-    num_slides -= 1
     assert response.status_code == codes.NO_CONTENT
     # Checks that there are fewer slides
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] == num_slides
+    assert body["count"] == 3
 
     # Tries to delete slide again
     response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers)
     assert response.status_code == codes.NOT_FOUND
 
     # Changes the order to the same order
-    i = 0
-    SID = body["items"][i]["id"]
-    order = body["items"][i]["order"]
+    SID = body["items"][0]["id"]
+    order = body["items"][0]["order"]
     response, _ = put(client, f"/api/competitions/{CID}/slides/{SID}/order", {"order": order}, headers=headers)
     assert response.status_code == codes.OK
 
diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py
index 7cbdec87..6b6bf7a0 100644
--- a/server/tests/test_helpers.py
+++ b/server/tests/test_helpers.py
@@ -9,23 +9,29 @@ from app.database.models import City, Role
 def add_default_values():
     media_types = ["Image", "Video"]
     question_types = ["Boolean", "Multiple", "Text"]
+    component_types = ["Text", "Image"]
+    view_types = ["Team", "Judge", "Audience"]
+
     roles = ["Admin", "Editor"]
     cities = ["Linköping", "Testköping"]
 
-    # Add media types
-    for item in media_types:
-        dbc.add.mediaType(item)
+    for name in media_types:
+        dbc.add.mediaType(name)
+
+    for name in question_types:
+        dbc.add.questionType(name)
+
+    for name in component_types:
+        dbc.add.componentType(name)
+
+    for name in view_types:
+        dbc.add.viewType(name)
 
-    # Add question types
-    for item in question_types:
-        dbc.add.questionType(item)
+    for name in roles:
+        dbc.add.role(name)
 
-    # Add roles
-    for item in roles:
-        dbc.add.role(item)
-    # Add cities
-    for item in cities:
-        dbc.add.city(item)
+    for name in cities:
+        dbc.add.city(name)
 
     item_admin = Role.query.filter(Role.name == "Admin").one()
     item_city = City.query.filter(City.name == "Linköping").one()
-- 
GitLab


From 79562da05a28e23eb28c0a793bc64b6524d9aee5 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Fri, 16 Apr 2021 19:59:01 +0200
Subject: [PATCH 14/21] add: reducer for all types

---
 client/src/Main.tsx                  |  9 ++++++++-
 client/src/actions/types.ts          |  1 +
 client/src/actions/typesAction.ts    | 15 ++++++++++++++
 client/src/interfaces/ApiModels.ts   | 11 ++++++++++-
 client/src/pages/admin/AdminPage.tsx | 15 +++++++-------
 client/src/reducers/allReducers.ts   |  2 ++
 client/src/reducers/typesReducer.ts  | 29 ++++++++++++++++++++++++++++
 7 files changed, 72 insertions(+), 10 deletions(-)
 create mode 100644 client/src/actions/typesAction.ts
 create mode 100644 client/src/reducers/typesReducer.ts

diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index f32aad9f..45286d54 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -1,5 +1,7 @@
-import React from 'react'
+import React, { useEffect } from 'react'
 import { BrowserRouter, Route, Switch } from 'react-router-dom'
+import { getTypes } from './actions/typesAction'
+import { useAppDispatch } from './hooks'
 import AdminPage from './pages/admin/AdminPage'
 import LoginPage from './pages/login/LoginPage'
 import PresentationEditorPage from './pages/presentationEditor/PresentationEditorPage'
@@ -11,6 +13,11 @@ import ViewSelectPage from './pages/views/ViewSelectPage'
 import SecureRoute from './utils/SecureRoute'
 
 const Main: React.FC = () => {
+  const dispatch = useAppDispatch()
+  useEffect(() => {
+    dispatch(getTypes())
+  }, [])
+
   return (
     <BrowserRouter>
       <Switch>
diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts
index a8736c4b..d1084c20 100644
--- a/client/src/actions/types.ts
+++ b/client/src/actions/types.ts
@@ -23,4 +23,5 @@ export default {
   SET_CITIES: 'SET_CITIES',
   SET_CITIES_TOTAL: 'SET_CITIES_TOTAL',
   SET_CITIES_COUNT: 'SET_CITIES_COUNT',
+  SET_TYPES: 'SET_TYPES',
 }
diff --git a/client/src/actions/typesAction.ts b/client/src/actions/typesAction.ts
new file mode 100644
index 00000000..4fcde4f5
--- /dev/null
+++ b/client/src/actions/typesAction.ts
@@ -0,0 +1,15 @@
+import axios from 'axios'
+import { AppDispatch } from './../store'
+import Types from './types'
+
+export const getTypes = () => async (dispatch: AppDispatch) => {
+  await axios
+    .get('/misc/types')
+    .then((res) => {
+      dispatch({
+        type: Types.SET_TYPES,
+        payload: res.data,
+      })
+    })
+    .catch((err) => console.log(err))
+}
diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts
index 194fb2e7..0ffc91af 100644
--- a/client/src/interfaces/ApiModels.ts
+++ b/client/src/interfaces/ApiModels.ts
@@ -1,4 +1,4 @@
-interface NameID {
+export interface NameID {
   id: number
   name: string
 }
@@ -6,6 +6,15 @@ export interface City extends NameID {}
 export interface Role extends NameID {}
 export interface MediaType extends NameID {}
 export interface QuestionType extends NameID {}
+export interface ComponentType extends NameID {}
+export interface ViewType extends NameID {}
+
+export interface AllTypes {
+  media_types: MediaType[]
+  question_types: QuestionType[]
+  component_types: ComponentType[]
+  view_types: ViewType[]
+}
 
 export interface Media {
   id: number
diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx
index a0dbe2bd..60c0fde2 100644
--- a/client/src/pages/admin/AdminPage.tsx
+++ b/client/src/pages/admin/AdminPage.tsx
@@ -57,12 +57,16 @@ const AdminView: React.FC = () => {
   const classes = useStyles()
   const [openIndex, setOpenIndex] = React.useState(0)
   const { path, url } = useRouteMatch()
+  const currentUser = useAppSelector((state) => state.user.userInfo)
+  const isAdmin = () => currentUser && currentUser.role.name === 'Admin'
+  const dispatch = useAppDispatch()
   const handleLogout = () => {
     dispatch(logoutUser())
   }
-  const dispatch = useAppDispatch()
-  const currentUser = useAppSelector((state) => state.user.userInfo)
-  const isAdmin = () => currentUser && currentUser.role.name === 'Admin'
+  useEffect(() => {
+    dispatch(getCities())
+    dispatch(getRoles())
+  }, [])
 
   const menuAdminItems = [
     { text: 'Startsida', icon: DashboardIcon },
@@ -93,11 +97,6 @@ const AdminView: React.FC = () => {
     ))
   }
 
-  useEffect(() => {
-    dispatch(getCities())
-    dispatch(getRoles())
-  }, [])
-
   return (
     <div className={classes.root}>
       <CssBaseline />
diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts
index 94743ff1..d0f9e801 100644
--- a/client/src/reducers/allReducers.ts
+++ b/client/src/reducers/allReducers.ts
@@ -6,6 +6,7 @@ import competitionsReducer from './competitionsReducer'
 import presentationReducer from './presentationReducer'
 import rolesReducer from './rolesReducer'
 import searchUserReducer from './searchUserReducer'
+import typesReducer from './typesReducer'
 import uiReducer from './uiReducer'
 import userReducer from './userReducer'
 
@@ -18,5 +19,6 @@ const allReducers = combineReducers({
   presentation: presentationReducer,
   roles: rolesReducer,
   searchUsers: searchUserReducer,
+  types: typesReducer,
 })
 export default allReducers
diff --git a/client/src/reducers/typesReducer.ts b/client/src/reducers/typesReducer.ts
new file mode 100644
index 00000000..3540ef86
--- /dev/null
+++ b/client/src/reducers/typesReducer.ts
@@ -0,0 +1,29 @@
+import { AnyAction } from 'redux'
+import Types from '../actions/types'
+import { ComponentType, MediaType, QuestionType, ViewType } from '../interfaces/ApiModels'
+
+interface TypesState {
+  componentTypes: ComponentType[]
+  viewTypes: ViewType[]
+  questionTypes: QuestionType[]
+  mediaTypes: MediaType[]
+}
+const initialState: TypesState = {
+  componentTypes: [],
+  viewTypes: [],
+  questionTypes: [],
+  mediaTypes: [],
+}
+
+export default function (state = initialState, action: AnyAction) {
+  switch (action.type) {
+    case Types.SET_TYPES:
+      state.componentTypes = action.payload.component_types as ComponentType[]
+      state.viewTypes = action.payload.view_types as ViewType[]
+      state.questionTypes = action.payload.question_types as QuestionType[]
+      state.mediaTypes = action.payload.media_types as MediaType[]
+      return state
+    default:
+      return state
+  }
+}
-- 
GitLab


From ec5459432d2697fd51d5d8522ee0a73792001e22 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Fri, 16 Apr 2021 20:09:44 +0200
Subject: [PATCH 15/21] moved getTypes to admin

---
 client/src/Main.tsx                  | 9 +--------
 client/src/pages/admin/AdminPage.tsx | 2 ++
 2 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/client/src/Main.tsx b/client/src/Main.tsx
index 45286d54..f32aad9f 100644
--- a/client/src/Main.tsx
+++ b/client/src/Main.tsx
@@ -1,7 +1,5 @@
-import React, { useEffect } from 'react'
+import React from 'react'
 import { BrowserRouter, Route, Switch } from 'react-router-dom'
-import { getTypes } from './actions/typesAction'
-import { useAppDispatch } from './hooks'
 import AdminPage from './pages/admin/AdminPage'
 import LoginPage from './pages/login/LoginPage'
 import PresentationEditorPage from './pages/presentationEditor/PresentationEditorPage'
@@ -13,11 +11,6 @@ import ViewSelectPage from './pages/views/ViewSelectPage'
 import SecureRoute from './utils/SecureRoute'
 
 const Main: React.FC = () => {
-  const dispatch = useAppDispatch()
-  useEffect(() => {
-    dispatch(getTypes())
-  }, [])
-
   return (
     <BrowserRouter>
       <Switch>
diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx
index 60c0fde2..8bbb68fa 100644
--- a/client/src/pages/admin/AdminPage.tsx
+++ b/client/src/pages/admin/AdminPage.tsx
@@ -20,6 +20,7 @@ import React, { useEffect } from 'react'
 import { Link, Route, Switch, useRouteMatch } from 'react-router-dom'
 import { getCities } from '../../actions/cities'
 import { getRoles } from '../../actions/roles'
+import { getTypes } from '../../actions/typesAction'
 import { logoutUser } from '../../actions/user'
 import { useAppDispatch, useAppSelector } from '../../hooks'
 import CompetitionManager from './competitions/CompetitionManager'
@@ -66,6 +67,7 @@ const AdminView: React.FC = () => {
   useEffect(() => {
     dispatch(getCities())
     dispatch(getRoles())
+    dispatch(getTypes())
   }, [])
 
   const menuAdminItems = [
-- 
GitLab


From fc3aec3c9c31e00f4a393d94775107a6014fb7c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se>
Date: Fri, 16 Apr 2021 21:15:51 +0200
Subject: [PATCH 16/21] Move question api to competitions/cid/slides/sid

---
 server/app/apis/__init__.py  |  6 ++----
 server/app/apis/questions.py | 29 ++++++++++++++---------------
 server/app/core/parsers.py   |  1 -
 server/tests/test_app.py     | 31 ++++++++++++++++---------------
 4 files changed, 32 insertions(+), 35 deletions(-)

diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index 9e3bd034..b7172f96 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -59,8 +59,6 @@ flask_api.add_namespace(user_ns, path="/api/users")
 flask_api.add_namespace(auth_ns, path="/api/auth")
 flask_api.add_namespace(comp_ns, path="/api/competitions")
 flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides")
-flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components/")
-
 flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams")
-flask_api.add_namespace(question_ns, path="/api/competitions/<CID>/questions")
-# flask_api.add_namespace(question_ns, path="/api/competitions/<CID>/slides/<SID>/question")
+flask_api.add_namespace(question_ns, path="/api/competitions/<CID>")
+flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components/")
diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py
index 86929bb4..48857dcb 100644
--- a/server/app/apis/questions.py
+++ b/server/app/apis/questions.py
@@ -12,7 +12,7 @@ schema = QuestionDTO.schema
 list_schema = QuestionDTO.list_schema
 
 
-@api.route("/")
+@api.route("/questions")
 @api.param("CID")
 class QuestionsList(Resource):
     @jwt_required
@@ -20,40 +20,39 @@ class QuestionsList(Resource):
         items = dbc.get.question_list(CID)
         return list_response(list_schema.dump(items))
 
+
+@api.route("/slides/<SID>/questions")
+@api.param("CID, SID")
+class QuestionsList(Resource):
     @jwt_required
-    def post(self, CID):
+    def post(self, SID, CID):
         args = question_parser.parse_args(strict=True)
 
-        name = args.get("name")
-        total_score = args.get("total_score")
-        type_id = args.get("type_id")
-        slide_id = args.get("slide_id")
-
-        item_slide = dbc.get.slide(CID, slide_id)
-        item = dbc.add.question(name, total_score, type_id, item_slide)
+        item_slide = dbc.get.slide(CID, SID)
+        item = dbc.add.question(item_slide=item_slide, **args)
 
         return item_response(schema.dump(item))
 
 
-@api.route("/<QID>")
-@api.param("CID,QID")
+@api.route("/slides/<SID>/questions/<QID>")
+@api.param("CID, SID, QID")
 class Questions(Resource):
     @jwt_required
-    def get(self, CID, QID):
+    def get(self, CID, SID, QID):
         item_question = dbc.get.question(CID, QID)
         return item_response(schema.dump(item_question))
 
     @jwt_required
-    def put(self, CID, QID):
+    def put(self, CID, SID, QID):
         args = question_parser.parse_args(strict=True)
 
         item_question = dbc.get.question(CID, QID)
-        item_question = dbc.edit.question(item_question, **args)
+        item_question = dbc.edit.question(item_question, slide_id=SID, **args)
 
         return item_response(schema.dump(item_question))
 
     @jwt_required
-    def delete(self, CID, QID):
+    def delete(self, CID, SID, QID):
         item_question = dbc.get.question(CID, QID)
         dbc.delete.question(item_question)
         return {}, codes.NO_CONTENT
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
index 151924ca..527961e5 100644
--- a/server/app/core/parsers.py
+++ b/server/app/core/parsers.py
@@ -57,7 +57,6 @@ slide_parser.add_argument("timer", type=int, default=None, location="json")
 question_parser = reqparse.RequestParser()
 question_parser.add_argument("name", type=str, default=None, location="json")
 question_parser.add_argument("total_score", type=int, default=None, location="json")
-question_parser.add_argument("slide_id", type=int, default=None, location="json")
 question_parser.add_argument("type_id", type=int, default=None, location="json")
 
 
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 67dbac72..7effe7ca 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -2,7 +2,8 @@ import app.core.http_codes as codes
 from app.database.models import Slide
 
 from tests import app, client, db
-from tests.test_helpers import add_default_values, change_order_test, delete, get, post, put
+from tests.test_helpers import (add_default_values, change_order_test, delete,
+                                get, post, put)
 
 
 def test_misc_api(client):
@@ -313,6 +314,7 @@ def test_question_api(client):
 
     # Get questions from empty competition
     CID = 1  # TODO: Fix api-calls so that the ones not using CID don't require one
+    SID = 1
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     assert response.status_code == codes.OK
     assert body["count"] == 0
@@ -322,7 +324,7 @@ def test_question_api(client):
     num_questions = 3
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     assert response.status_code == codes.OK
-    print(body)
+    # print(body)
     assert body["count"] == num_questions
 
     # # Get specific question
@@ -349,11 +351,11 @@ def test_question_api(client):
     name = "Nytt namn"
     # total_score = 2
     type_id = 2
-    slide_id = 5
+    SID = 5
     response, item_question = post(
         client,
-        f"/api/competitions/{CID}/questions",
-        {"name": name, "type_id": type_id, "slide_id": slide_id},
+        f"/api/competitions/{CID}/slides/{SID}/questions",
+        {"name": name, "type_id": type_id},
         headers=headers,
     )
     num_questions += 1
@@ -361,7 +363,7 @@ def test_question_api(client):
     assert item_question["name"] == name
     # # assert item_question["total_score"] == total_score
     assert item_question["type"]["id"] == type_id
-    assert item_question["slide_id"] == slide_id
+    assert item_question["slide_id"] == SID
     # Checks number of questions
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     assert response.status_code == codes.OK
@@ -369,12 +371,12 @@ def test_question_api(client):
 
     # Try to get question in another competition
     QID = 1
-    response, item_question = get(client, f"/api/competitions/{CID}/questions/{QID}", headers=headers)
+    response, item_question = get(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
     assert response.status_code == codes.NOT_FOUND
 
     # Get question
     QID = 4
-    response, item_question = get(client, f"/api/competitions/{CID}/questions/{QID}", headers=headers)
+    response, item_question = get(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
     assert response.status_code == codes.OK
     assert item_question["id"] == QID
 
@@ -386,9 +388,8 @@ def test_question_api(client):
     QID = 1
     response, _ = put(
         client,
-        f"/api/competitions/{CID}/questions/{QID}",
-        # {"name": name, "total_score": total_score, "type_id": type_id, "slide_id": slide_id},
-        {"name": name, "type_id": type_id, "slide_id": slide_id},
+        f"/api/competitions/{CID}/slides/{SID}/questions/{QID}",
+        {"name": name, "type_id": type_id},
         headers=headers,
     )
     assert response.status_code == codes.NOT_FOUND
@@ -409,9 +410,9 @@ def test_question_api(client):
     assert item_question["slide_id"] != slide_id
     response, item_question = put(
         client,
-        f"/api/competitions/{CID}/questions/{QID}",
+        f"/api/competitions/{CID}/slides/{SID}/questions/{QID}",
         # {"name": name, "total_score": total_score, "type_id": type_id, "slide_id": slide_id},
-        {"name": name, "type_id": type_id, "slide_id": slide_id},
+        {"name": name, "type_id": type_id},
         headers=headers,
     )
     assert response.status_code == codes.OK
@@ -425,7 +426,7 @@ def test_question_api(client):
     assert body["count"] == num_questions
 
     # Delete question
-    response, _ = delete(client, f"/api/competitions/{CID}/questions/{QID}", headers=headers)
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
     num_questions -= 1
     assert response.status_code == codes.NO_CONTENT
 
@@ -435,5 +436,5 @@ def test_question_api(client):
     assert body["count"] == num_questions
 
     # Tries to delete question again
-    response, _ = delete(client, f"/api/competitions/{CID}/questions/{QID}", headers=headers)
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
     assert response.status_code == codes.NOT_FOUND
-- 
GitLab


From 0f4818a49801bcffb21755c229e0ff7a1679e30b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se>
Date: Fri, 16 Apr 2021 22:50:21 +0200
Subject: [PATCH 17/21] Update questions api

---
 server/app/apis/questions.py          |  9 +++++----
 server/app/core/parsers.py            |  2 +-
 server/app/database/controller/get.py |  4 ++--
 server/tests/test_app.py              | 19 ++++++++++---------
 4 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py
index 48857dcb..55db2819 100644
--- a/server/app/apis/questions.py
+++ b/server/app/apis/questions.py
@@ -27,6 +27,7 @@ class QuestionsList(Resource):
     @jwt_required
     def post(self, SID, CID):
         args = question_parser.parse_args(strict=True)
+        del args["slide_id"]
 
         item_slide = dbc.get.slide(CID, SID)
         item = dbc.add.question(item_slide=item_slide, **args)
@@ -39,20 +40,20 @@ class QuestionsList(Resource):
 class Questions(Resource):
     @jwt_required
     def get(self, CID, SID, QID):
-        item_question = dbc.get.question(CID, QID)
+        item_question = dbc.get.question(CID, SID, QID)
         return item_response(schema.dump(item_question))
 
     @jwt_required
     def put(self, CID, SID, QID):
         args = question_parser.parse_args(strict=True)
 
-        item_question = dbc.get.question(CID, QID)
-        item_question = dbc.edit.question(item_question, slide_id=SID, **args)
+        item_question = dbc.get.question(CID, SID, QID)
+        item_question = dbc.edit.question(item_question, **args)
 
         return item_response(schema.dump(item_question))
 
     @jwt_required
     def delete(self, CID, SID, QID):
-        item_question = dbc.get.question(CID, QID)
+        item_question = dbc.get.question(CID, SID, QID)
         dbc.delete.question(item_question)
         return {}, codes.NO_CONTENT
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
index 527961e5..54f54239 100644
--- a/server/app/core/parsers.py
+++ b/server/app/core/parsers.py
@@ -58,7 +58,7 @@ question_parser = reqparse.RequestParser()
 question_parser.add_argument("name", type=str, default=None, location="json")
 question_parser.add_argument("total_score", type=int, default=None, location="json")
 question_parser.add_argument("type_id", type=int, default=None, location="json")
-
+question_parser.add_argument("slide_id", type=int, location="json")
 
 ###TEAM####
 team_parser = reqparse.RequestParser()
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index 892d251b..1669f0ae 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -56,9 +56,9 @@ def team(CID, TID, required=True, error_msg=None):
     return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first_extended(required, error_msg)
 
 
-def question(CID, QID, required=True, error_msg=None):
+def question(CID, SID, QID, required=True, error_msg=None):
     return (
-        Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == Question.slide_id))
+        Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == SID) & (Slide.id == Question.slide_id))
         .filter(Question.id == QID)
         .first_extended(required, error_msg)
     )
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 7effe7ca..8086774e 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -2,8 +2,7 @@ import app.core.http_codes as codes
 from app.database.models import Slide
 
 from tests import app, client, db
-from tests.test_helpers import (add_default_values, change_order_test, delete,
-                                get, post, put)
+from tests.test_helpers import add_default_values, change_order_test, delete, get, post, put
 
 
 def test_misc_api(client):
@@ -376,6 +375,7 @@ def test_question_api(client):
 
     # Get question
     QID = 4
+    SID = 4
     response, item_question = get(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
     assert response.status_code == codes.OK
     assert item_question["id"] == QID
@@ -384,7 +384,7 @@ def test_question_api(client):
     name = "Nyare namn"
     # total_score = 2
     type_id = 3
-    slide_id = 1
+    SID = 1
     QID = 1
     response, _ = put(
         client,
@@ -402,31 +402,32 @@ def test_question_api(client):
     name = "Nyare namn"
     # total_score = 2
     type_id = 3
-    slide_id = 5
+    SID = 4
+    NEW_SID = 5
     QID = 4
     assert item_question["name"] != name
     # assert item_question["total_score"] != total_score
     assert item_question["type"]["id"] != type_id
-    assert item_question["slide_id"] != slide_id
+    assert item_question["slide_id"] != NEW_SID
     response, item_question = put(
         client,
         f"/api/competitions/{CID}/slides/{SID}/questions/{QID}",
         # {"name": name, "total_score": total_score, "type_id": type_id, "slide_id": slide_id},
-        {"name": name, "type_id": type_id},
+        {"name": name, "type_id": type_id, "slide_id": NEW_SID},
         headers=headers,
     )
     assert response.status_code == codes.OK
     assert item_question["name"] == name
     # # assert item_question["total_score"] == total_score
     assert item_question["type"]["id"] == type_id
-    assert item_question["slide_id"] == slide_id
+    assert item_question["slide_id"] == NEW_SID
     # Checks number of questions
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     assert response.status_code == codes.OK
     assert body["count"] == num_questions
 
     # Delete question
-    response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_SID}/questions/{QID}", headers=headers)
     num_questions -= 1
     assert response.status_code == codes.NO_CONTENT
 
@@ -436,5 +437,5 @@ def test_question_api(client):
     assert body["count"] == num_questions
 
     # Tries to delete question again
-    response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_SID}/questions/{QID}", headers=headers)
     assert response.status_code == codes.NOT_FOUND
-- 
GitLab


From a2f39b662e8ecf4a6db1433728d43a9f9c7aa960 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se>
Date: Sat, 17 Apr 2021 00:30:22 +0200
Subject: [PATCH 18/21] Add api for competition codes

---
 server/app/apis/__init__.py            |  2 ++
 server/app/apis/auth.py                | 17 +++++++++-
 server/app/apis/codes.py               | 44 ++++++++++++++++++++++++++
 server/app/core/codes.py               | 15 +++++++++
 server/app/core/dto.py                 |  6 ++++
 server/app/core/parsers.py             |  6 ++++
 server/app/core/rich_schemas.py        | 10 ++++++
 server/app/core/schemas.py             | 10 ++++++
 server/app/database/controller/add.py  |  7 ++++
 server/app/database/controller/edit.py | 15 +++++++++
 server/app/database/controller/get.py  | 23 ++++++++++++++
 server/app/database/models.py          |  8 ++++-
 12 files changed, 161 insertions(+), 2 deletions(-)
 create mode 100644 server/app/apis/codes.py
 create mode 100644 server/app/core/codes.py

diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index b7172f96..5d6e45dc 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -43,6 +43,7 @@ def item_response(item, code=codes.OK):
 from flask_restx import Api
 
 from .auth import api as auth_ns
+from .codes import api as code_ns
 from .competitions import api as comp_ns
 from .components import api as component_ns
 from .media import api as media_ns
@@ -60,5 +61,6 @@ flask_api.add_namespace(auth_ns, path="/api/auth")
 flask_api.add_namespace(comp_ns, path="/api/competitions")
 flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides")
 flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams")
+flask_api.add_namespace(code_ns, path="/api/competitions/<CID>/codes")
 flask_api.add_namespace(question_ns, path="/api/competitions/<CID>")
 flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components/")
diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py
index 1510df64..10d820f8 100644
--- a/server/app/apis/auth.py
+++ b/server/app/apis/auth.py
@@ -1,7 +1,8 @@
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import admin_required, item_response, text_response
-from app.core.dto import AuthDTO
+from app.core.codes import verify_code
+from app.core.dto import AuthDTO, CodeDTO
 from app.core.parsers import create_user_parser, login_parser
 from app.database.models import User
 from flask_jwt_extended import (
@@ -69,6 +70,20 @@ class AuthLogin(Resource):
         return response
 
 
+@api.route("/login/<code>")
+@api.param("code")
+class AuthLogin(Resource):
+    def post(self, code):
+        if not verify_code(code):
+            api.abort(codes.BAD_REQUEST, "Invalid code")
+
+        item_code = dbc.get.code_by_code(code)
+        if not item_code:
+            api.abort(codes.UNAUTHORIZED, "A presentation with that code does not exist")
+
+        return item_response(CodeDTO.schema.dump(item_code)), codes.OK
+
+
 @api.route("/logout")
 class AuthLogout(Resource):
     @jwt_required
diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py
new file mode 100644
index 00000000..70cf9341
--- /dev/null
+++ b/server/app/apis/codes.py
@@ -0,0 +1,44 @@
+import app.database.controller as dbc
+from app.apis import admin_required, item_response, list_response
+from app.core import http_codes as codes
+from app.core.codes import generate_code
+from app.core.dto import CodeDTO
+from app.core.parsers import code_parser
+from app.database.models import Competition
+from flask_jwt_extended import jwt_required
+from flask_restx import Resource
+
+api = CodeDTO.api
+schema = CodeDTO.schema
+list_schema = CodeDTO.list_schema
+
+
+@api.route("/")
+@api.param("CID")
+class CodesList(Resource):
+    @jwt_required
+    def get(self, CID):
+        items = dbc.get.code_list(CID)
+        return list_response(list_schema.dump(items), len(items)), codes.OK
+
+    @jwt_required
+    def post(self, CID):
+        args = code_parser.parse_args(strict=True)
+        item = dbc.add.code(**args)
+        return item_response(schema.dump(item)), codes.OK
+
+
+@api.route("/<code_id>")
+@api.param("CID, code_id")
+class Competitions(Resource):
+    @jwt_required
+    def put(self, CID, code_id):
+        item_code = dbc.get.code(code_id)
+        item_code = dbc.edit.generate_new_code(item_code)
+        return item_response(schema.dump(item_code)), codes.OK
+
+    # @jwt_required
+    # def delete(self, CID, code_id):
+    #     item_code = dbc.get.code(code_id)
+    #     dbc.delete.code(item_code)
+    #     return {}, http_codes.NOT_FOUND
diff --git a/server/app/core/codes.py b/server/app/core/codes.py
new file mode 100644
index 00000000..c13fa84e
--- /dev/null
+++ b/server/app/core/codes.py
@@ -0,0 +1,15 @@
+import random
+import re
+import string
+
+CODE_LENGTH = 6
+ALLOWED_CHARS = string.ascii_uppercase + string.digits
+CODE_RE = re.compile(f"^[{ALLOWED_CHARS}]{{{CODE_LENGTH}}}$")
+
+
+def generate_code():
+    return "".join(random.choices(ALLOWED_CHARS, k=CODE_LENGTH))
+
+
+def verify_code(c):
+    return CODE_RE.search(c.upper()) is not None
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index a6899fc7..4533fc0c 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -37,6 +37,12 @@ class CompetitionDTO:
     list_schema = schemas.CompetitionSchema(many=True)
 
 
+class CodeDTO:
+    api = Namespace("codes")
+    schema = rich_schemas.CodeSchemaRich(many=False)
+    list_schema = schemas.CodeSchema(many=True)
+
+
 class SlideDTO:
     api = Namespace("slides")
     schema = schemas.SlideSchema(many=False)
diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py
index 54f54239..f691ea8d 100644
--- a/server/app/core/parsers.py
+++ b/server/app/core/parsers.py
@@ -60,6 +60,12 @@ question_parser.add_argument("total_score", type=int, default=None, location="js
 question_parser.add_argument("type_id", type=int, default=None, location="json")
 question_parser.add_argument("slide_id", type=int, location="json")
 
+###QUESTION####
+code_parser = reqparse.RequestParser()
+code_parser.add_argument("pointer", type=str, default=None, location="json")
+code_parser.add_argument("view_type_id", type=int, default=None, location="json")
+
+
 ###TEAM####
 team_parser = reqparse.RequestParser()
 team_parser.add_argument("name", type=str, location="json")
diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py
index 8c220e78..fa6daac9 100644
--- a/server/app/core/rich_schemas.py
+++ b/server/app/core/rich_schemas.py
@@ -43,6 +43,16 @@ class TeamSchemaRich(RichSchema):
     question_answers = fields.Nested(schemas.QuestionAnswerSchema, many=True)
 
 
+class CodeSchemaRich(RichSchema):
+    class Meta(RichSchema.Meta):
+        model = models.Code
+
+    id = ma.auto_field()
+    code = ma.auto_field()
+    pointer = ma.auto_field()
+    view_type = fields.Nested(schemas.ViewTypeSchema, many=False)
+
+
 class SlideSchemaRich(RichSchema):
     class Meta(RichSchema.Meta):
         model = models.Slide
diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py
index ee909e4b..4f9646a5 100644
--- a/server/app/core/schemas.py
+++ b/server/app/core/schemas.py
@@ -31,6 +31,16 @@ class ComponentTypeSchema(IdNameSchema):
         model = models.ComponentType
 
 
+class CodeSchema(IdNameSchema):
+    class Meta(BaseSchema.Meta):
+        model = models.Code
+
+    id = ma.auto_field()
+    code = ma.auto_field()
+    pointer = ma.auto_field()
+    view_type_id = ma.auto_field()
+
+
 class ViewTypeSchema(IdNameSchema):
     class Meta(BaseSchema.Meta):
         model = models.ViewType
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 0e955a9e..07b49fde 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -1,8 +1,10 @@
 import app.core.http_codes as codes
 from app.core import db
+from app.core.codes import generate_code
 from app.database.models import (
     Blacklist,
     City,
+    Code,
     Competition,
     Component,
     ComponentType,
@@ -75,6 +77,11 @@ def team(name, item_competition):
     return Team(name, item_competition.id)
 
 
+@db_add
+def code(pointer, view_type_id):
+    return Code(pointer, view_type_id)
+
+
 @db_add
 def mediaType(name):
     return MediaType(name)
diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py
index 3afcb4d4..7b3ef334 100644
--- a/server/app/database/controller/edit.py
+++ b/server/app/database/controller/edit.py
@@ -1,4 +1,6 @@
 from app.core import db
+from app.core.codes import generate_code
+from app.database.models import Code
 
 
 def switch_order(item1, item2):
@@ -109,3 +111,16 @@ def question(item_question, name=None, total_score=None, type_id=None, slide_id=
     db.session.refresh(item_question)
 
     return item_question
+
+
+def generate_new_code(item_code):
+
+    code = generate_code()
+    while db.session.query(Code).filter(Code.code == code).count():
+        code = generate_code()
+
+    item_code.code = code
+    db.session.commit()
+    db.session.refresh(item_code)
+
+    return item_code
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index 1669f0ae..4e9a6aa5 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -1,6 +1,7 @@
 from app.core import db
 from app.database.models import (
     City,
+    Code,
     Competition,
     Component,
     ComponentType,
@@ -11,8 +12,11 @@ from app.database.models import (
     Slide,
     Team,
     User,
+    ViewType,
 )
 
+team_view_id = ViewType.query.filter(ViewType.name == "Team").one().id
+
 
 def all(db_type):
     return db_type.query.all()
@@ -34,6 +38,25 @@ def competition(CID, required=True, error_msg=None):
     return Competition.query.filter(Competition.id == CID).first_extended(required, error_msg)
 
 
+def code(code_id, required=True, error_msg=None):
+    return Code.query.filter(Code.id == code_id).first_extended(required, error_msg)
+
+
+def code_by_code(code):
+    return Code.query.filter(Code.code == code.upper()).first()
+
+
+def code_list(competition_id):
+    return (
+        Code.query.join(Team, (Code.view_type_id == team_view_id) & (Team.id == Code.pointer), isouter=True)
+        .filter(
+            ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id))
+            | ((Code.view_type_id == team_view_id) & (competition_id == Team.competition_id))
+        )
+        .all()
+    )
+
+
 def user(UID, required=True, error_msg=None):
     return User.query.filter(User.id == UID).first_extended(required, error_msg)
 
diff --git a/server/app/database/models.py b/server/app/database/models.py
index 6396e035..9479d92c 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -1,6 +1,7 @@
 import json
 
 from app.core import bcrypt, db
+from app.core.codes import generate_code
 from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
 from sqlalchemy.orm import backref
 from sqlalchemy.types import TypeDecorator
@@ -230,7 +231,12 @@ class Code(db.Model):
 
     view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False)
 
-    def __init__(self, code, pointer, view_type_id):
+    def __init__(self, pointer, view_type_id):
+
+        code = generate_code()
+        while db.session.query(Code).filter(Code.code == code).count():
+            code = generate_code()
+
         self.code = code
         self.pointer = pointer
         self.view_type_id = view_type_id
-- 
GitLab


From d595561cf292be2659ecfc7b1fdb897c3d5c67b7 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Sat, 17 Apr 2021 13:21:23 +0200
Subject: [PATCH 19/21] fix: dubble slashes in component url

---
 server/app/apis/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index 9e3bd034..4e0f34df 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -59,7 +59,7 @@ flask_api.add_namespace(user_ns, path="/api/users")
 flask_api.add_namespace(auth_ns, path="/api/auth")
 flask_api.add_namespace(comp_ns, path="/api/competitions")
 flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides")
-flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components/")
+flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components")
 
 flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams")
 flask_api.add_namespace(question_ns, path="/api/competitions/<CID>/questions")
-- 
GitLab


From fb0f6fc2c84fa17b951be1b8f6a807b6adcfa353 Mon Sep 17 00:00:00 2001
From: Emil <Emil>
Date: Sat, 17 Apr 2021 16:44:03 +0200
Subject: [PATCH 20/21] Fixed tests, updated api models

---
 client/src/interfaces/ApiModels.ts            | 17 +++------
 client/src/interfaces/ApiRichModels.ts        | 11 +++---
 client/src/interfaces/Competition.ts          | 10 ------
 .../PresentationEditorPage.test.tsx           | 35 +++++++++++++++++--
 .../PresentationEditorPage.tsx                |  2 ++
 .../components/CompetitionSettings.test.tsx   |  5 ++-
 .../components/CompetitionSettings.tsx        |  9 ++---
 .../components/SettingsPanel.test.tsx         | 19 ++++++++--
 .../components/SlideSettings.test.tsx         | 11 +++++-
 .../components/SlideSettings.tsx              | 10 +++---
 .../src/reducers/presentationReducer.test.ts  |  5 +--
 11 files changed, 83 insertions(+), 51 deletions(-)
 delete mode 100644 client/src/interfaces/Competition.ts

diff --git a/client/src/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts
index a154a0c8..d423d424 100644
--- a/client/src/interfaces/ApiModels.ts
+++ b/client/src/interfaces/ApiModels.ts
@@ -2,18 +2,11 @@ interface NameID {
   id: number
   name: string
 }
-export interface City extends NameID {
-  users: User[]
-  competitions: Competition[]
-}
+export interface City extends NameID {}
 
-export interface Role extends NameID {
-  users: User[]
-}
+export interface Role extends NameID {}
 
-export interface QuestionType extends NameID {
-  questions: Question[]
-}
+export interface QuestionType extends NameID {}
 
 export interface Media {
   id: number
@@ -22,9 +15,7 @@ export interface Media {
   user_id: number
 }
 
-export interface MediaType extends NameID {
-  media: Media[]
-}
+export interface MediaType extends NameID {}
 
 export interface User extends NameID {
   email: string
diff --git a/client/src/interfaces/ApiRichModels.ts b/client/src/interfaces/ApiRichModels.ts
index 779dbd8a..e21ae5fa 100644
--- a/client/src/interfaces/ApiRichModels.ts
+++ b/client/src/interfaces/ApiRichModels.ts
@@ -1,4 +1,5 @@
-import { QuestionAlternative, QuestionAnswer } from './ApiModels'
+import { Component } from 'react'
+import { Media, QuestionAlternative, QuestionAnswer, QuestionType } from './ApiModels'
 
 export interface RichCompetition {
   name: string
@@ -15,9 +16,9 @@ export interface RichSlide {
   timer: number
   title: string
   competition_id: number
+  components: Component[]
+  medias: Media[]
   questions: RichQuestion[]
-  body: string
-  settings: string
 }
 
 export interface RichTeam {
@@ -33,7 +34,7 @@ export interface RichQuestion {
   name: string
   title: string
   total_score: number
+  question_type: QuestionType
   type_id: number
-  question_answers: QuestionAnswer[]
-  alternatives: QuestionAlternative[]
+  question_alternatives: QuestionAlternative[]
 }
diff --git a/client/src/interfaces/Competition.ts b/client/src/interfaces/Competition.ts
deleted file mode 100644
index c7c4d309..00000000
--- a/client/src/interfaces/Competition.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { City } from './ApiModels'
-import { RichSlide } from './ApiRichModels'
-
-export interface Competition {
-  name: string
-  id: number
-  city: City
-  year: number
-  slides: RichSlide[]
-}
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx
index 67e38b99..ebc96d73 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.test.tsx
@@ -1,12 +1,41 @@
 import { render } from '@testing-library/react'
+import mockedAxios from 'axios'
 import React from 'react'
+import { Provider } from 'react-redux'
 import { BrowserRouter } from 'react-router-dom'
+import store from '../../store'
 import PresentationEditorPage from './PresentationEditorPage'
 
 it('renders presentation editor', () => {
+  const competitionRes: any = {
+    data: {
+      name: '',
+      id: 0,
+      year: 0,
+      city_id: 0,
+      slides: [],
+      teams: [],
+    },
+  }
+  const citiesRes: any = {
+    data: {
+      items: [
+        {
+          name: '',
+          city_id: 0,
+        },
+      ],
+    },
+  }
+  ;(mockedAxios.get as jest.Mock).mockImplementation((path: string, params?: any) => {
+    if (path.startsWith('/competitions')) return Promise.resolve(competitionRes)
+    return Promise.resolve(citiesRes)
+  })
   render(
-    <BrowserRouter>
-      <PresentationEditorPage />
-    </BrowserRouter>
+    <Provider store={store}>
+      <BrowserRouter>
+        <PresentationEditorPage />
+      </BrowserRouter>
+    </Provider>
   )
 })
diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
index e31aaabb..2993b12b 100644
--- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx
+++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx
@@ -7,6 +7,7 @@ import ListItemText from '@material-ui/core/ListItemText'
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import React, { useEffect } from 'react'
 import { useParams } from 'react-router-dom'
+import { getCities } from '../../actions/cities'
 import { getEditorCompetition } from '../../actions/editor'
 import { useAppDispatch, useAppSelector } from '../../hooks'
 import { Content } from '../views/styled'
@@ -67,6 +68,7 @@ const PresentationEditorPage: React.FC = () => {
   // TODO: wait for dispatch to finish
   useEffect(() => {
     dispatch(getEditorCompetition(id))
+    dispatch(getCities())
   }, [])
   return (
     <PresentationEditorContainer>
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx
index 592581d4..a655a30f 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.test.tsx
@@ -1,7 +1,10 @@
 import { render } from '@testing-library/react'
 import React from 'react'
+import { BrowserRouter } from 'react-router-dom'
 import ImageComponentDisplay from './ImageComponentDisplay'
 
 it('renders image component display', () => {
-  render(<ImageComponentDisplay component={{ id: 0, x: 0, y: 0, w: 0, h: 0, type: 0, media_id: 0 }} />)
+  render(
+  <ImageComponentDisplay component={{ id: 0, x: 0, y: 0, w: 0, h: 0, type: 0, media_id: 0 }} />
+  )
 })
diff --git a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
index e5e1bc6a..2c61633f 100644
--- a/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/CompetitionSettings.tsx
@@ -13,9 +13,8 @@ import {
 import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
 import CloseIcon from '@material-ui/icons/Close'
 import axios from 'axios'
-import React, { useEffect } from 'react'
+import React from 'react'
 import { useParams } from 'react-router-dom'
-import { getCities } from '../../../actions/cities'
 import { getEditorCompetition } from '../../../actions/editor'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
 import { City } from '../../../interfaces/ApiModels'
@@ -65,10 +64,6 @@ const CompetitionSettings: React.FC = () => {
   const { id }: CompetitionParams = useParams()
   const dispatch = useAppDispatch()
   const competition = useAppSelector((state) => state.editor.competition)
-  useEffect(() => {
-    dispatch(getEditorCompetition(id))
-    dispatch(getCities())
-  }, [])
 
   const updateCompetitionName = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
     await axios
@@ -122,7 +117,7 @@ const CompetitionSettings: React.FC = () => {
           <InputLabel id="region-selection-label">Region</InputLabel>
           {/*TODO: fixa så cities laddar in i statet likt i CompetitionManager*/}
           <Select
-            value={cities[competition.city_id - 1] ? cities[0].name : ''}
+            value={cities.find((city) => city.id === competition.city_id)?.name || ''}
             label="RegionSelect"
             onChange={handleChange}
           >
diff --git a/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx b/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx
index b4daa57b..a7a71e25 100644
--- a/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx
+++ b/client/src/pages/presentationEditor/components/SettingsPanel.test.tsx
@@ -1,15 +1,30 @@
 import { render } from '@testing-library/react'
 import { mount } from 'enzyme'
 import React from 'react'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import store from '../../../store'
 import CompetitionSettings from './CompetitionSettings'
 import SettingsPanel from './SettingsPanel'
 
 it('renders settings panel', () => {
-  render(<SettingsPanel />)
+  render(
+    <Provider store={store}>
+      <BrowserRouter>
+        <SettingsPanel />
+      </BrowserRouter>
+    </Provider>
+  )
 })
 
 it('renders slide settings tab', () => {
-  const wrapper = mount(<SettingsPanel />)
+  const wrapper = mount(
+    <Provider store={store}>
+      <BrowserRouter>
+        <SettingsPanel />
+      </BrowserRouter>
+    </Provider>
+  )
   const tabs = wrapper.find('.MuiTabs-flexContainer')
   expect(wrapper.find(CompetitionSettings).length).toEqual(1)
   tabs.children().at(1).simulate('click')
diff --git a/client/src/pages/presentationEditor/components/SlideSettings.test.tsx b/client/src/pages/presentationEditor/components/SlideSettings.test.tsx
index 4099e130..a271fcf5 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.test.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.test.tsx
@@ -1,7 +1,16 @@
 import { render } from '@testing-library/react'
 import React from 'react'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import store from '../../../store'
 import SlideSettings from './SlideSettings'
 
 it('renders slide settings', () => {
-  render(<SlideSettings />)
+  render(
+    <Provider store={store}>
+      <BrowserRouter>
+        <SlideSettings />
+      </BrowserRouter>
+    </Provider>
+  )
 })
diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx
index d5ab4912..cd611060 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx
@@ -114,7 +114,7 @@ const SlideSettings: React.FC = () => {
   const updateSlideType = async (event: React.ChangeEvent<{ value: unknown }>) => {
     await axios
       // TODO: implementera API för att kunna ändra i questions->type_id
-      .put(`/competitions/${id}/slides/${currentSlide.id}`, { type_id: event.target.value })
+      .put(`/competitions/${id}/slides/${currentSlide?.id}`, { type_id: event.target.value })
       .then(() => {
         dispatch(getEditorCompetition(id))
       })
@@ -125,7 +125,7 @@ const SlideSettings: React.FC = () => {
     // Wheter the alternative is true or false
     await axios
       // TODO: implementera API för att kunna ändra i alternatives->value
-      .put(`/competitions/${id}/slides/${currentSlide.id}`, { value: event.target.value })
+      .put(`/competitions/${id}/slides/${currentSlide?.id}`, { value: event.target.value })
       .then(() => {
         dispatch(getEditorCompetition(id))
       })
@@ -169,7 +169,7 @@ const SlideSettings: React.FC = () => {
       <div className={classes.whiteBackground}>
         <FormControl variant="outlined" className={classes.dropDown}>
           <InputLabel id="slide-type-selection-label">Sidtyp</InputLabel>
-          <Select value={currentSlide.questions[0].type_id} label="Sidtyp" onChange={updateSlideType}>
+          <Select value={currentSlide?.questions[0].type_id || 0} label="Sidtyp" onChange={updateSlideType}>
             <MenuItem value={0}>
               <Button>Informationssida</Button>
             </MenuItem>
@@ -194,7 +194,7 @@ const SlideSettings: React.FC = () => {
           helperText="Lämna blank för att inte använda timerfunktionen"
           label="Timer"
           type="number"
-          value={currentSlide.timer}
+          value={currentSlide?.timer}
         />
       </ListItem>
 
@@ -206,7 +206,7 @@ const SlideSettings: React.FC = () => {
             secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)"
           />
         </ListItem>
-        {currentSlide.questions[0].alternatives.map((alt) => (
+        {(currentSlide?.questions[0].question_alternatives || []).map((alt) => (
           <div key={alt.id}>
             <ListItem divider>
               <TextField
diff --git a/client/src/reducers/presentationReducer.test.ts b/client/src/reducers/presentationReducer.test.ts
index cf4ded41..15bcd337 100644
--- a/client/src/reducers/presentationReducer.test.ts
+++ b/client/src/reducers/presentationReducer.test.ts
@@ -7,10 +7,7 @@ const initialState = {
   competition: {
     name: '',
     id: 0,
-    city: {
-      id: 0,
-      name: '',
-    },
+    city_id: 0,
     slides: [],
     year: 0,
     teams: [],
-- 
GitLab


From 33331109040187531ee11f4aa99f993b0419ef03 Mon Sep 17 00:00:00 2001
From: robban64 <carl@schonfelder.se>
Date: Sat, 17 Apr 2021 16:49:43 +0200
Subject: [PATCH 21/21] fix: slide order

---
 server/app/__init__.py                     |   2 +-
 server/app/apis/__init__.py                |   4 +-
 server/app/apis/codes.py                   |  24 +---
 server/app/apis/competitions.py            |   6 +-
 server/app/apis/components.py              |  24 ++--
 server/app/apis/misc.py                    |   2 +-
 server/app/apis/slides.py                  |  30 ++---
 server/app/apis/teams.py                   |   2 +-
 server/app/core/__init__.py                |   2 +-
 server/app/core/codes.py                   |   4 +-
 server/app/core/dto.py                     |   4 +-
 server/app/database/__init__.py            |  55 ++++++++
 server/app/database/base.py                |  37 ------
 server/app/database/controller/__init__.py |  15 +--
 server/app/database/controller/add.py      | 113 ++++++++---------
 server/app/database/controller/delete.py   |  22 +++-
 server/app/database/controller/edit.py     |  15 ---
 server/app/database/controller/get.py      |  86 ++++---------
 server/app/database/controller/utils.py    |  23 ++++
 server/app/database/models.py              |  27 +---
 server/populate.py                         |  15 +--
 server/tests/test_app.py                   | 140 ++++-----------------
 server/tests/test_helpers.py               |  49 ++------
 23 files changed, 270 insertions(+), 431 deletions(-)
 delete mode 100644 server/app/database/base.py
 create mode 100644 server/app/database/controller/utils.py

diff --git a/server/app/__init__.py b/server/app/__init__.py
index 8aa7a08a..a2529395 100644
--- a/server/app/__init__.py
+++ b/server/app/__init__.py
@@ -1,5 +1,5 @@
 from flask import Flask, redirect, request
-from flask_uploads import IMAGES, UploadSet, configure_uploads
+from flask_uploads import configure_uploads
 
 import app.database.models as models
 from app.core import bcrypt, db, jwt, ma
diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py
index 50281141..b48b8b33 100644
--- a/server/app/apis/__init__.py
+++ b/server/app/apis/__init__.py
@@ -23,7 +23,7 @@ def admin_required():
 
 
 def text_response(message, code=codes.OK):
-    return {"message": message}, codes.OK
+    return {"message": message}, code
 
 
 def list_response(items, total=None, code=codes.OK):
@@ -63,4 +63,4 @@ flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides")
 flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams")
 flask_api.add_namespace(code_ns, path="/api/competitions/<CID>/codes")
 flask_api.add_namespace(question_ns, path="/api/competitions/<CID>")
-flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components")
+flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SOrder>/components")
diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py
index 70cf9341..af6aee84 100644
--- a/server/app/apis/codes.py
+++ b/server/app/apis/codes.py
@@ -1,10 +1,9 @@
 import app.database.controller as dbc
 from app.apis import admin_required, item_response, list_response
 from app.core import http_codes as codes
-from app.core.codes import generate_code
 from app.core.dto import CodeDTO
 from app.core.parsers import code_parser
-from app.database.models import Competition
+from app.database.models import Code, Competition
 from flask_jwt_extended import jwt_required
 from flask_restx import Resource
 
@@ -21,24 +20,13 @@ class CodesList(Resource):
         items = dbc.get.code_list(CID)
         return list_response(list_schema.dump(items), len(items)), codes.OK
 
-    @jwt_required
-    def post(self, CID):
-        args = code_parser.parse_args(strict=True)
-        item = dbc.add.code(**args)
-        return item_response(schema.dump(item)), codes.OK
-
 
 @api.route("/<code_id>")
 @api.param("CID, code_id")
-class Competitions(Resource):
+class CodesById(Resource):
     @jwt_required
     def put(self, CID, code_id):
-        item_code = dbc.get.code(code_id)
-        item_code = dbc.edit.generate_new_code(item_code)
-        return item_response(schema.dump(item_code)), codes.OK
-
-    # @jwt_required
-    # def delete(self, CID, code_id):
-    #     item_code = dbc.get.code(code_id)
-    #     dbc.delete.code(item_code)
-    #     return {}, http_codes.NOT_FOUND
+        item = dbc.get.one(Code, code_id)
+        item.code = dbc.utils.generate_unique_code()
+        dbc.utils.commit_and_refresh(item)
+        return item_response(schema.dump(item)), codes.OK
diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py
index be376206..26b6f363 100644
--- a/server/app/apis/competitions.py
+++ b/server/app/apis/competitions.py
@@ -30,20 +30,20 @@ class CompetitionsList(Resource):
 class Competitions(Resource):
     @jwt_required
     def get(self, CID):
-        item = dbc.get.competition(CID)
+        item = dbc.get.one(Competition, CID)
         return item_response(schema.dump(item))
 
     @jwt_required
     def put(self, CID):
         args = competition_parser.parse_args(strict=True)
-        item = dbc.get.competition(CID)
+        item = dbc.get.one(Competition, CID)
         item = dbc.edit.competition(item, **args)
 
         return item_response(schema.dump(item))
 
     @jwt_required
     def delete(self, CID):
-        item = dbc.get.competition(CID)
+        item = dbc.get.one(Competition, CID)
         dbc.delete.competition(item)
 
         return "deleted"
diff --git a/server/app/apis/components.py b/server/app/apis/components.py
index da211895..f88e1e2d 100644
--- a/server/app/apis/components.py
+++ b/server/app/apis/components.py
@@ -3,7 +3,7 @@ import app.database.controller as dbc
 from app.apis import admin_required, item_response, list_response
 from app.core.dto import ComponentDTO
 from app.core.parsers import component_create_parser, component_parser
-from app.database.models import Competition
+from app.database.models import Competition, Component
 from flask.globals import request
 from flask_jwt_extended import jwt_required
 from flask_restx import Resource
@@ -14,37 +14,37 @@ list_schema = ComponentDTO.list_schema
 
 
 @api.route("/<component_id>")
-@api.param("CID, SID, component_id")
+@api.param("CID, SOrder, component_id")
 class ComponentByID(Resource):
     @jwt_required
-    def get(self, CID, SID, component_id):
-        item = dbc.get.component(component_id)
+    def get(self, CID, SOrder, component_id):
+        item = dbc.get.one(Component, component_id)
         return item_response(schema.dump(item))
 
     @jwt_required
-    def put(self, CID, SID, component_id):
+    def put(self, CID, SOrder, component_id):
         args = component_parser.parse_args()
         item = dbc.edit.component(**args)
         return item_response(schema.dump(item))
 
     @jwt_required
-    def delete(self, CID, SID, component_id):
-        item = dbc.get.component(component_id)
+    def delete(self, CID, SOrder, component_id):
+        item = dbc.get.one(Component, component_id)
         dbc.delete.component(item)
         return {}, codes.NO_CONTENT
 
 
 @api.route("/")
-@api.param("CID, SID")
+@api.param("CID, SOrder")
 class ComponentList(Resource):
     @jwt_required
-    def get(self, CID, SID):
-        items = dbc.get.component_list(SID)
+    def get(self, CID, SOrder):
+        items = dbc.get.component_list(SOrder)
         return list_response(list_schema.dump(items))
 
     @jwt_required
-    def post(self, CID, SID):
+    def post(self, CID, SOrder):
         args = component_create_parser.parse_args()
-        item_slide = dbc.get.slide(CID, SID)
+        item_slide = dbc.get.slide(CID, SOrder)
         item = dbc.add.component(item_slide=item_slide, **args)
         return item_response(schema.dump(item))
diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py
index b40f97a8..a78c2f8c 100644
--- a/server/app/apis/misc.py
+++ b/server/app/apis/misc.py
@@ -63,7 +63,7 @@ class Cities(Resource):
         item = dbc.get.one(City, ID)
         args = name_parser.parse_args(strict=True)
         item.name = args["name"]
-        dbc.commit_and_refresh(item)
+        dbc.utils.commit_and_refresh(item)
         items = dbc.get.all(City)
         return list_response(city_schema.dump(items))
 
diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py
index 972e1bd6..9aeb793a 100644
--- a/server/app/apis/slides.py
+++ b/server/app/apis/slides.py
@@ -22,49 +22,49 @@ class SlidesList(Resource):
 
     @jwt_required
     def post(self, CID):
-        item_comp = dbc.get.competition(CID)
+        item_comp = dbc.get.one(Competition, CID)
         item_slide = dbc.add.slide(item_comp)
         dbc.add.question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide)
-        dbc.refresh(item_comp)
+        dbc.utils.refresh(item_comp)
         return list_response(list_schema.dump(item_comp.slides))
 
 
-@api.route("/<SID>")
-@api.param("CID,SID")
+@api.route("/<SOrder>")
+@api.param("CID,SOrder")
 class Slides(Resource):
     @jwt_required
-    def get(self, CID, SID):
-        item_slide = dbc.get.slide(CID, SID)
+    def get(self, CID, SOrder):
+        item_slide = dbc.get.slide(CID, SOrder)
         return item_response(schema.dump(item_slide))
 
     @jwt_required
-    def put(self, CID, SID):
+    def put(self, CID, SOrder):
         args = slide_parser.parse_args(strict=True)
         title = args.get("title")
         timer = args.get("timer")
 
-        item_slide = dbc.get.slide(CID, SID)
+        item_slide = dbc.get.slide(CID, SOrder)
         item_slide = dbc.edit.slide(item_slide, title, timer)
 
         return item_response(schema.dump(item_slide))
 
     @jwt_required
-    def delete(self, CID, SID):
-        item_slide = dbc.get.slide(CID, SID)
+    def delete(self, CID, SOrder):
+        item_slide = dbc.get.slide(CID, SOrder)
 
         dbc.delete.slide(item_slide)
         return {}, codes.NO_CONTENT
 
 
-@api.route("/<SID>/order")
-@api.param("CID,SID")
+@api.route("/<SOrder>/order")
+@api.param("CID,SOrder")
 class SlidesOrder(Resource):
     @jwt_required
-    def put(self, CID, SID):
+    def put(self, CID, SOrder):
         args = slide_parser.parse_args(strict=True)
         order = args.get("order")
 
-        item_slide = dbc.get.slide(CID, SID)
+        item_slide = dbc.get.slide(CID, SOrder)
 
         if order == item_slide.order:
             return item_response(schema.dump(item_slide))
@@ -77,7 +77,7 @@ class SlidesOrder(Resource):
             order = order_count - 1
 
         # get slide at the requested order
-        item_slide_order = dbc.get.slide_by_order(CID, order)
+        item_slide_order = dbc.get.slide(CID, order)
 
         # switch place between them
         item_slide = dbc.edit.switch_order(item_slide, item_slide_order)
diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py
index 9600e2a4..6729ccf8 100644
--- a/server/app/apis/teams.py
+++ b/server/app/apis/teams.py
@@ -23,7 +23,7 @@ class TeamsList(Resource):
     @jwt_required
     def post(self, CID):
         args = team_parser.parse_args(strict=True)
-        item_comp = dbc.get.competition(CID)
+        item_comp = dbc.get.one(Competition,CID)
         item_team = dbc.add.team(args["name"], item_comp)
         return item_response(schema.dump(item_team))
 
diff --git a/server/app/core/__init__.py b/server/app/core/__init__.py
index 09c321ef..acfca554 100644
--- a/server/app/core/__init__.py
+++ b/server/app/core/__init__.py
@@ -1,4 +1,4 @@
-from app.database.base import Base, ExtendedQuery
+from app.database import Base, ExtendedQuery
 from flask_bcrypt import Bcrypt
 from flask_jwt_extended.jwt_manager import JWTManager
 from flask_marshmallow import Marshmallow
diff --git a/server/app/core/codes.py b/server/app/core/codes.py
index c13fa84e..47150767 100644
--- a/server/app/core/codes.py
+++ b/server/app/core/codes.py
@@ -3,11 +3,11 @@ import re
 import string
 
 CODE_LENGTH = 6
-ALLOWED_CHARS = string.ascii_uppercase + string.digits
+ALLOWED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 CODE_RE = re.compile(f"^[{ALLOWED_CHARS}]{{{CODE_LENGTH}}}$")
 
 
-def generate_code():
+def generate_code_string():
     return "".join(random.choices(ALLOWED_CHARS, k=CODE_LENGTH))
 
 
diff --git a/server/app/core/dto.py b/server/app/core/dto.py
index 4533fc0c..6541ef75 100644
--- a/server/app/core/dto.py
+++ b/server/app/core/dto.py
@@ -1,9 +1,7 @@
 import app.core.rich_schemas as rich_schemas
 import app.core.schemas as schemas
-import marshmallow as ma
-from flask_restx import Namespace, fields
+from flask_restx import Namespace
 from flask_uploads import IMAGES, UploadSet
-from sqlalchemy.sql.expression import true
 
 
 class ComponentDTO:
diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py
index e69de29b..ead77d9c 100644
--- a/server/app/database/__init__.py
+++ b/server/app/database/__init__.py
@@ -0,0 +1,55 @@
+import json
+
+from flask_restx import abort
+from flask_sqlalchemy import BaseQuery
+from flask_sqlalchemy.model import Model
+from sqlalchemy import Column, DateTime, Text
+from sqlalchemy.sql import func
+from sqlalchemy.types import TypeDecorator
+
+
+class Base(Model):
+    __abstract__ = True
+    _created = Column(DateTime(timezone=True), server_default=func.now())
+    _updated = Column(DateTime(timezone=True), onupdate=func.now())
+
+
+class ExtendedQuery(BaseQuery):
+    def first_extended(self, required=True, error_message=None, error_code=404):
+        item = self.first()
+
+        if required and not item:
+            if not error_message:
+                error_message = "Object not found"
+            abort(error_code, error_message)
+
+        return item
+
+    def pagination(self, page=0, page_size=15, order_column=None, order=1):
+        query = self
+        if order_column:
+            if order == 1:
+                query = query.order_by(order_column)
+            else:
+                query = query.order_by(order_column.desc())
+
+        total = query.count()
+        query = query.limit(page_size).offset(page * page_size)
+        items = query.all()
+        return items, total
+
+
+class Dictionary(TypeDecorator):
+
+    impl = Text(1024)
+
+    def process_bind_param(self, value, dialect):
+        if value is not None:
+            value = json.dumps(value).replace("'", '"')
+
+        return value
+
+    def process_result_value(self, value, dialect):
+        if value is not None:
+            value = json.loads(value)
+        return value
diff --git a/server/app/database/base.py b/server/app/database/base.py
deleted file mode 100644
index 7a16a752..00000000
--- a/server/app/database/base.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import app.core.http_codes as codes
-import sqlalchemy as sa
-from flask_restx import abort
-from flask_sqlalchemy import BaseQuery, SQLAlchemy
-from flask_sqlalchemy.model import Model
-from sqlalchemy.sql import func
-
-
-class Base(Model):
-    __abstract__ = True
-    _created = sa.Column(sa.DateTime(timezone=True), server_default=func.now())
-    _updated = sa.Column(sa.DateTime(timezone=True), onupdate=func.now())
-
-
-class ExtendedQuery(BaseQuery):
-    def first_extended(self, required=True, error_message=None, error_code=codes.NOT_FOUND):
-        item = self.first()
-
-        if required and not item:
-            if not error_message:
-                error_message = "Object not found"
-            abort(error_code, error_message)
-
-        return item
-
-    def pagination(self, page=0, page_size=15, order_column=None, order=1):
-        query = self
-        if order_column:
-            if order == 1:
-                query = query.order_by(order_column)
-            else:
-                query = query.order_by(order_column.desc())
-
-        total = query.count()
-        query = query.limit(page_size).offset(page * page_size)
-        items = query.all()
-        return items, total
diff --git a/server/app/database/controller/__init__.py b/server/app/database/controller/__init__.py
index d31ded56..2865b523 100644
--- a/server/app/database/controller/__init__.py
+++ b/server/app/database/controller/__init__.py
@@ -1,16 +1,3 @@
 # import add, get
 from app.core import db
-from app.database.controller import add, delete, edit, get, search
-
-
-def commit_and_refresh(item):
-    db.session.commit()
-    db.session.refresh(item)
-
-
-def refresh(item):
-    db.session.refresh(item)
-
-
-def commit(item):
-    db.session.commit()
+from app.database.controller import add, delete, edit, get, search, utils
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 07b49fde..755849bb 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -1,6 +1,6 @@
 import app.core.http_codes as codes
 from app.core import db
-from app.core.codes import generate_code
+from app.database.controller import utils
 from app.database.models import (
     Blacklist,
     City,
@@ -21,92 +21,93 @@ from app.database.models import (
 from flask_restx import abort
 
 
-def db_add(func):
-    def wrapper(*args, **kwargs):
-        item = func(*args, **kwargs)
-        db.session.add(item)
-        db.session.commit()
-        db.session.refresh(item)
+def db_add(item):
+    db.session.add(item)
+    db.session.commit()
+    db.session.refresh(item)
 
-        if not item:
-            abort(codes.BAD_REQUEST, f"Object could not be created")
+    if not item:
+        abort(codes.BAD_REQUEST, f"Object could not be created")
 
-        return item
+    return item
 
-    return wrapper
 
+def blacklist(jti):
+    return db_add(Blacklist(jti))
 
-@db_add
-def component(type_id, item_slide, data, x=0, y=0, w=0, h=0):
-    return Component(item_slide.id, type_id, data, x, y, w, h)
 
+def mediaType(name):
+    return db_add(MediaType(name))
 
-@db_add
-def blacklist(jti):
-    return Blacklist(jti)
 
+def questionType(name):
+    return db_add(QuestionType(name))
 
-@db_add
-def image(filename, user_id):
-    return Media(filename, 1, user_id)
 
+def componentType(name):
+    return db_add(ComponentType(name))
 
-@db_add
-def slide(item_competition):
-    order = Slide.query.filter(Slide.competition_id == item_competition.id).count()  # first element has index 0
-    return Slide(order, item_competition.id)
 
+def viewType(name):
+    return db_add(ViewType(name))
 
-@db_add
-def user(email, password, role_id, city_id, name=None):
-    return User(email, password, role_id, city_id, name)
 
+def role(name):
+    return db_add(Role(name))
 
-@db_add
-def question(name, total_score, type_id, item_slide):
-    return Question(name, total_score, type_id, item_slide.id)
 
+def city(name):
+    return db_add(City(name))
 
-@db_add
-def competition(name, year, city_id):
-    return Competition(name, year, city_id)
+
+def component(type_id, item_slide, data, x=0, y=0, w=0, h=0):
+    return db_add(Component(item_slide.id, type_id, data, x, y, w, h))
 
 
-@db_add
-def team(name, item_competition):
-    return Team(name, item_competition.id)
+def image(filename, user_id):
+    return db_add(Media(filename, 1, user_id))
+
+
+def user(email, password, role_id, city_id, name=None):
+    return db_add(User(email, password, role_id, city_id, name))
+
+
+def question(name, total_score, type_id, item_slide):
+    return db_add(Question(name, total_score, type_id, item_slide.id))
 
 
-@db_add
 def code(pointer, view_type_id):
-    return Code(pointer, view_type_id)
+    code_string = utils.generate_unique_code()
+    return db_add(Code(code_string, pointer, view_type_id))
 
 
-@db_add
-def mediaType(name):
-    return MediaType(name)
+def team(name, item_competition):
+    item = db_add(Team(name, item_competition.id))
 
+    # Add code for the team
+    code(item.id, 1)
 
-@db_add
-def questionType(name):
-    return QuestionType(name)
+    return item
 
 
-@db_add
-def componentType(name):
-    return ComponentType(name)
+def slide(item_competition):
+    order = Slide.query.filter(Slide.competition_id == item_competition.id).count()  # first element has index 0
+    return db_add(Slide(order, item_competition.id))
 
 
-@db_add
-def viewType(name):
-    return ViewType(name)
+def competition(name, year, city_id):
+    item_competition = db_add(Competition(name, year, city_id))
 
+    # Add one slide for the competition
+    slide(item_competition)
 
-@db_add
-def role(name):
-    return Role(name)
+    # Add code for Judge view
+    code(item_competition.id, 2)
 
+    # Add code for Audience view
+    code(item_competition.id, 3)
 
-@db_add
-def city(name):
-    return City(name)
+    # Add two teams
+
+    utils.refresh(item_competition)
+    return item_competition
diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py
index 2eb040c9..33bea217 100644
--- a/server/app/database/controller/delete.py
+++ b/server/app/database/controller/delete.py
@@ -12,18 +12,26 @@ def component(item_component):
     default(item_component)
 
 
-def slide(item_slide):
+def _slide(item_slide):
     for item_question in item_slide.questions:
         question(item_question)
 
-    deleted_slide_competition_id = item_slide.competition_id
-    deleted_slide_order = item_slide.order
+    for item_component in item_slide.components:
+        default(item_component)
+
     default(item_slide)
 
+
+def slide(item_slide):
+    competition_id = item_slide.competition_id
+    slide_order = item_slide.order
+
+    _slide(item_slide)
+
     # Update slide order for all slides after the deleted slide
-    slides_in_same_competition = dbc.get.slide_list(deleted_slide_competition_id)
+    slides_in_same_competition = dbc.get.slide_list(competition_id)
     for other_slide in slides_in_same_competition:
-        if other_slide.order > deleted_slide_order:
+        if other_slide.order > slide_order:
             other_slide.order -= 1
 
     db.session.commit()
@@ -53,7 +61,9 @@ def question_answers(item_question_answers):
 
 def competition(item_competition):
     for item_slide in item_competition.slides:
-        slide(item_slide)
+        _slide(item_slide)
     for item_team in item_competition.teams:
         team(item_team)
+
+    # TODO codes
     default(item_competition)
diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py
index 7b3ef334..3afcb4d4 100644
--- a/server/app/database/controller/edit.py
+++ b/server/app/database/controller/edit.py
@@ -1,6 +1,4 @@
 from app.core import db
-from app.core.codes import generate_code
-from app.database.models import Code
 
 
 def switch_order(item1, item2):
@@ -111,16 +109,3 @@ def question(item_question, name=None, total_score=None, type_id=None, slide_id=
     db.session.refresh(item_question)
 
     return item_question
-
-
-def generate_new_code(item_code):
-
-    code = generate_code()
-    while db.session.query(Code).filter(Code.code == code).count():
-        code = generate_code()
-
-    item_code.code = code
-    db.session.commit()
-    db.session.refresh(item_code)
-
-    return item_code
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index 4e9a6aa5..269640b3 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -1,21 +1,8 @@
 from app.core import db
-from app.database.models import (
-    City,
-    Code,
-    Competition,
-    Component,
-    ComponentType,
-    MediaType,
-    Question,
-    QuestionType,
-    Role,
-    Slide,
-    Team,
-    User,
-    ViewType,
-)
-
-team_view_id = ViewType.query.filter(ViewType.name == "Team").one().id
+from app.database.models import (City, Code, Competition, Component,
+                                 ComponentType, MediaType, Question,
+                                 QuestionType, Role, Slide, Team, User,
+                                 ViewType)
 
 
 def all(db_type):
@@ -29,34 +16,10 @@ def one(db_type, id, required=True, error_msg=None):
 def user_exists(email):
     return User.query.filter(User.email == email).count() > 0
 
-
-def component(ID, required=True, error_msg=None):
-    return Component.query.filter(Component.id == ID).first_extended(required, error_msg)
-
-
-def competition(CID, required=True, error_msg=None):
-    return Competition.query.filter(Competition.id == CID).first_extended(required, error_msg)
-
-
-def code(code_id, required=True, error_msg=None):
-    return Code.query.filter(Code.id == code_id).first_extended(required, error_msg)
-
-
 def code_by_code(code):
     return Code.query.filter(Code.code == code.upper()).first()
 
 
-def code_list(competition_id):
-    return (
-        Code.query.join(Team, (Code.view_type_id == team_view_id) & (Team.id == Code.pointer), isouter=True)
-        .filter(
-            ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id))
-            | ((Code.view_type_id == team_view_id) & (competition_id == Team.competition_id))
-        )
-        .all()
-    )
-
-
 def user(UID, required=True, error_msg=None):
     return User.query.filter(User.id == UID).first_extended(required, error_msg)
 
@@ -65,30 +28,38 @@ def user_by_email(email, required=True, error_msg=None):
     return User.query.filter(User.email == email).first_extended(required, error_msg)
 
 
-def slide_by_order(CID, order, required=True, error_msg=None):
-    return Slide.query.filter((Slide.competition_id == CID) & (Slide.order == order)).first_extended(
-        required, error_msg
-    )
-
-
-def slide(CID, SID, required=True, error_msg=None):
-    return Slide.query.filter((Slide.competition_id == CID) & (Slide.id == SID)).first_extended(required, error_msg)
+def slide(CID, SOrder, required=True, error_msg=None):
+    filters = (Slide.competition_id == CID) & (Slide.order == SOrder)
+    return Slide.query.filter(filters).first_extended(required, error_msg)
 
 
 def team(CID, TID, required=True, error_msg=None):
     return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first_extended(required, error_msg)
 
 
-def question(CID, SID, QID, required=True, error_msg=None):
-    return (
-        Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == SID) & (Slide.id == Question.slide_id))
-        .filter(Question.id == QID)
-        .first_extended(required, error_msg)
+def question(CID, SOrder, QID, required=True, error_msg=None):
+    join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Slide.id == Question.slide_id)
+    return Question.query.join(Slide, join_filters).filter(Question.id == QID).first_extended(required, error_msg)
+
+
+
+def code_list(competition_id):
+    team_view_id = 1
+    join_filters = (Code.view_type_id == team_view_id) & (Team.id == Code.pointer)
+    filters = ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id))(
+        (Code.view_type_id == team_view_id) & (competition_id == Team.competition_id)
     )
+    return Code.query.join(Team, join_filters, isouter=True).filter(filters).all()
 
 
 def question_list(CID):
-    return Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == Question.slide_id)).all()
+    join_filters = (Slide.competition_id == CID) & (Slide.id == Question.slide_id)
+    return Question.query.join(Slide, join_filters).all()
+
+
+def component_list(CID, SOrder):
+    join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Component.slide_id == Slide.id)
+    return Component.query.join(Slide, join_filters).all()
 
 
 def team_list(CID):
@@ -99,10 +70,5 @@ def slide_list(CID):
     return Slide.query.filter(Slide.competition_id == CID).all()
 
 
-def component_list(SID):
-    # TODO: Maybe take CID as argument and make sure that SID is in that competition?
-    return Component.query.filter(Component.slide_id == SID).all()
-
-
 def slide_count(CID):
     return Slide.query.filter(Slide.competition_id == CID).count()
diff --git a/server/app/database/controller/utils.py b/server/app/database/controller/utils.py
new file mode 100644
index 00000000..61be34ca
--- /dev/null
+++ b/server/app/database/controller/utils.py
@@ -0,0 +1,23 @@
+from app.core import db
+from app.core.codes import generate_code_string
+from app.database.models import Code
+
+
+def generate_unique_code():
+    code = generate_code_string()
+    while db.session.query(Code).filter(Code.code == code).count():
+        code = generate_code_string()
+    return code
+
+
+def commit_and_refresh(item):
+    db.session.commit()
+    db.session.refresh(item)
+
+
+def refresh(item):
+    db.session.refresh(item)
+
+
+def commit(item):
+    db.session.commit()
diff --git a/server/app/database/models.py b/server/app/database/models.py
index 9479d92c..cfedd923 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -1,11 +1,6 @@
-import json
-
 from app.core import bcrypt, db
-from app.core.codes import generate_code
 from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
-from sqlalchemy.orm import backref
-from sqlalchemy.types import TypeDecorator
-
+from app.database import Dictionary
 STRING_SIZE = 254
 
 
@@ -187,20 +182,7 @@ class QuestionAnswer(db.Model):
         self.team_id = team_id
 
 
-class Dictionary(TypeDecorator):
-
-    impl = db.Text(1024)
-
-    def process_bind_param(self, value, dialect):
-        if value is not None:
-            value = json.dumps(value).replace("'", '"')
 
-        return value
-
-    def process_result_value(self, value, dialect):
-        if value is not None:
-            value = json.loads(value)
-        return value
 
 
 class Component(db.Model):
@@ -231,12 +213,7 @@ class Code(db.Model):
 
     view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False)
 
-    def __init__(self, pointer, view_type_id):
-
-        code = generate_code()
-        while db.session.query(Code).filter(Code.code == code).count():
-            code = generate_code()
-
+    def __init__(self, code, pointer, view_type_id):
         self.code = code
         self.pointer = pointer
         self.view_type_id = view_type_id
diff --git a/server/populate.py b/server/populate.py
index 45eb6005..bebcaa8b 100644
--- a/server/populate.py
+++ b/server/populate.py
@@ -1,8 +1,6 @@
-from sqlalchemy.sql.expression import true
-
 import app.database.controller as dbc
 from app import create_app, db
-from app.database.models import City, Competition, MediaType, QuestionType, Role
+from app.database.models import City, Competition, QuestionType, Role
 
 
 def _add_items():
@@ -49,14 +47,11 @@ def _add_items():
         dbc.add.competition(f"Test{i+1}", 1971, city_id)
 
     item_comps = Competition.query.all()
-    # Add
-    for item_comp in item_comps:
-        for i in range(3):
-            # Add slide to competition
-            item_slide = dbc.add.slide(item_comp)
 
-            # Add question to competition
-            dbc.add.question(f"Q{i+1}", i + 1, text_id, item_slide)
+    for item_comp in item_comps:
+        for item_slide in item_comp.slides:
+            for i in range(3):
+                dbc.add.question(f"Q{i+1}", i + 1, text_id, item_slide)
 
         # Add teams to competition
         for team_name in teams:
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 8086774e..948b9ae4 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -72,9 +72,6 @@ def test_competition_api(client):
     assert body["name"] == "c1"
     competition_id = body["id"]
 
-    # Save number of slides
-    num_slides = len(Slide.query.all())
-
     # Get competition
     response, body = get(client, f"/api/competitions/{competition_id}", headers=headers)
     assert response.status_code == codes.OK
@@ -85,11 +82,9 @@ def test_competition_api(client):
 
     response, body = get(client, f"/api/competitions/{competition_id}/slides", headers=headers)
     assert response.status_code == codes.OK
-    assert len(body["items"]) == 2
+    assert len(body["items"]) == 3
 
-    response, body = put(
-        client, f"/api/competitions/{competition_id}/slides/{num_slides}/order", {"order": 1}, headers=headers
-    )
+    response, body = put(client, f"/api/competitions/{competition_id}/slides/{2}/order", {"order": 1}, headers=headers)
     assert response.status_code == codes.OK
 
     response, body = post(client, f"/api/competitions/{competition_id}/teams", {"name": "t1"}, headers=headers)
@@ -235,7 +230,7 @@ def test_slide_api(client):
     CID = 1
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] == 0
+    assert body["count"] == 1
 
     # Get slides
     CID = 2
@@ -247,12 +242,14 @@ def test_slide_api(client):
     response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers)
     assert response.status_code == codes.OK
     assert body["count"] == 4
+    # Add another slide
+    response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers)
 
     # Get slide
-    SID = 1
-    response, item_slide = get(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers)
+    slide_order = 1
+    response, item_slide = get(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers)
     assert response.status_code == codes.OK
-    assert item_slide["id"] == SID
+    assert item_slide["order"] == slide_order
 
     # Edit slide
     order = 6
@@ -265,7 +262,7 @@ def test_slide_api(client):
     assert item_slide["timer"] != timer
     response, item_slide = put(
         client,
-        f"/api/competitions/{CID}/slides/{SID}",
+        f"/api/competitions/{CID}/slides/{slide_order}",
         # TODO: Implement so these commented lines can be edited
         # {"order": order, "title": title, "body": body, "timer": timer},
         {"title": title, "timer": timer},
@@ -278,29 +275,26 @@ def test_slide_api(client):
     assert item_slide["timer"] == timer
 
     # Delete slide
-    response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers)
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers)
     assert response.status_code == codes.NO_CONTENT
     # Checks that there are fewer slides
     response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers)
     assert response.status_code == codes.OK
-    assert body["count"] == 3
+    assert body["count"] == 4
 
-    # Tries to delete slide again
-    response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers)
-    assert response.status_code == codes.NOT_FOUND
+    # Tries to delete slide again, should work since the order is now changed
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers)
+    assert response.status_code == codes.NO_CONTENT
 
     # Changes the order to the same order
-    SID = body["items"][0]["id"]
-    order = body["items"][0]["order"]
-    response, _ = put(client, f"/api/competitions/{CID}/slides/{SID}/order", {"order": order}, headers=headers)
+    slide_order = body["items"][0]["order"]
+    response, _ = put(
+        client, f"/api/competitions/{CID}/slides/{slide_order}/order", {"order": slide_order}, headers=headers
+    )
     assert response.status_code == codes.OK
 
     # Changes the order
-    change_order_test(client, CID, SID, order + 1, headers)
-
-    # Changes order to 0
-    SID = 7
-    change_order_test(client, CID, SID, -1, headers)
+    change_order_test(client, CID, slide_order, slide_order + 1, headers)
 
 
 def test_question_api(client):
@@ -313,7 +307,7 @@ def test_question_api(client):
 
     # Get questions from empty competition
     CID = 1  # TODO: Fix api-calls so that the ones not using CID don't require one
-    SID = 1
+    slide_order = 1
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     assert response.status_code == codes.OK
     assert body["count"] == 0
@@ -323,111 +317,30 @@ def test_question_api(client):
     num_questions = 3
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     assert response.status_code == codes.OK
-    # print(body)
     assert body["count"] == num_questions
 
-    # # Get specific question
-    # name = "Q2"
-    # # total_score = 2
-    # type_id = 5
-    # slide_id = 5
-    # response, body = get(
-    #     client,
-    #     f"/api/competitions/{CID}/questions/",
-    #     headers=headers,
-    # )
-    # # print(f"357: {body['items']}")
-    # assert response.status_code == codes.OK
-    # assert body["count"] == 1
-    # item_question = body["items"][0]
-    # # print(f"338: {item_question}")
-    # assert item_question["name"] == name
-    # # assert item_question["total_score"] == total_score
-    # assert item_question["type_id"] == type_id
-    # assert item_question["slide_id"] == slide_id
-
     # Add question
     name = "Nytt namn"
-    # total_score = 2
     type_id = 2
-    SID = 5
+    slide_order = 1
     response, item_question = post(
         client,
-        f"/api/competitions/{CID}/slides/{SID}/questions",
+        f"/api/competitions/{CID}/slides/{slide_order}/questions",
         {"name": name, "type_id": type_id},
         headers=headers,
     )
-    num_questions += 1
+    num_questions = 4
     assert response.status_code == codes.OK
     assert item_question["name"] == name
-    # # assert item_question["total_score"] == total_score
     assert item_question["type"]["id"] == type_id
-    assert item_question["slide_id"] == SID
-    # Checks number of questions
-    response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == num_questions
-
-    # Try to get question in another competition
-    QID = 1
-    response, item_question = get(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
-    assert response.status_code == codes.NOT_FOUND
 
-    # Get question
-    QID = 4
-    SID = 4
-    response, item_question = get(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers)
-    assert response.status_code == codes.OK
-    assert item_question["id"] == QID
-
-    # Try to edit question in another competition
-    name = "Nyare namn"
-    # total_score = 2
-    type_id = 3
-    SID = 1
-    QID = 1
-    response, _ = put(
-        client,
-        f"/api/competitions/{CID}/slides/{SID}/questions/{QID}",
-        {"name": name, "type_id": type_id},
-        headers=headers,
-    )
-    assert response.status_code == codes.NOT_FOUND
-    # Checks number of questions
-    response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
-    assert response.status_code == codes.OK
-    assert body["count"] == num_questions
-
-    # Edit question
-    name = "Nyare namn"
-    # total_score = 2
-    type_id = 3
-    SID = 4
-    NEW_SID = 5
-    QID = 4
-    assert item_question["name"] != name
-    # assert item_question["total_score"] != total_score
-    assert item_question["type"]["id"] != type_id
-    assert item_question["slide_id"] != NEW_SID
-    response, item_question = put(
-        client,
-        f"/api/competitions/{CID}/slides/{SID}/questions/{QID}",
-        # {"name": name, "total_score": total_score, "type_id": type_id, "slide_id": slide_id},
-        {"name": name, "type_id": type_id, "slide_id": NEW_SID},
-        headers=headers,
-    )
-    assert response.status_code == codes.OK
-    assert item_question["name"] == name
-    # # assert item_question["total_score"] == total_score
-    assert item_question["type"]["id"] == type_id
-    assert item_question["slide_id"] == NEW_SID
     # Checks number of questions
     response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers)
     assert response.status_code == codes.OK
     assert body["count"] == num_questions
-
+    """
     # Delete question
-    response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_SID}/questions/{QID}", headers=headers)
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}/questions/{QID}", headers=headers)
     num_questions -= 1
     assert response.status_code == codes.NO_CONTENT
 
@@ -437,5 +350,6 @@ def test_question_api(client):
     assert body["count"] == num_questions
 
     # Tries to delete question again
-    response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_SID}/questions/{QID}", headers=headers)
+    response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_slide_order}/questions/{QID}", headers=headers)
     assert response.status_code == codes.NOT_FOUND
+    """
diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py
index 6b6bf7a0..fbd77d9e 100644
--- a/server/tests/test_helpers.py
+++ b/server/tests/test_helpers.py
@@ -39,23 +39,25 @@ def add_default_values():
     dbc.add.user("test@test.se", "password", item_admin.id, item_city.id, "Olle Olsson")
 
     # Add competitions
-    dbc.add.competition("Tom tävling", 2012, item_city.id)
+    item_competition = dbc.add.competition("Tom tävling", 2012, item_city.id)
     for j in range(2):
         item_comp = dbc.add.competition(f"Tävling {j}", 2012, item_city.id)
+        # Add two more slides to competition
+        dbc.add.slide(item_comp)
+        dbc.add.slide(item_comp)
 
         # Add slides
-        for i in range(len(question_types)):
-            # Add slide to competition
-            item_slide = dbc.add.slide(item_comp)
-
+        i = 1
+        for item_slide in item_comp.slides:
             # Populate slide with data
             item_slide.title = f"Title {i}"
             item_slide.body = f"Body {i}"
             item_slide.timer = 100 + i
             # item_slide.settings = "{}"
-
+            dbc.utils.commit_and_refresh(item_slide)
             # Add question to competition
-            dbc.add.question(name=f"Q{i+1}", total_score=i + 1, type_id=i + 1, item_slide=item_slide)
+            dbc.add.question(name=f"Q{i}", total_score=i, type_id=1, item_slide=item_slide)
+            i += 1
 
 
 def get_body(response):
@@ -124,37 +126,12 @@ def assert_object_values(obj, values):
 
 
 # Changes order of slides
-def change_order_test(client, cid, sid, order, h):
-    sid_at_order = -1
-    actual_order = 0 if order < 0 else order  # used to find the slide_id
-    response, body = get(client, f"/api/competitions/{cid}/slides", headers=h)
-    assert response.status_code == codes.OK
-
-    # Finds the slide_id of the slide that will be swapped with
-    for item_slide in body["items"]:
-        if item_slide["order"] == actual_order:
-            assert item_slide["id"] != sid
-            sid_at_order = item_slide["id"]
-    assert sid_at_order != -1
-
-    # Gets old versions of slides
-    response, item_slide_10 = get(client, f"/api/competitions/{cid}/slides/{sid}", headers=h)
+def change_order_test(client, cid, order, new_order, h):
+    response, new_order_body = get(client, f"/api/competitions/{cid}/slides/{new_order}", headers=h)
     assert response.status_code == codes.OK
-    response, item_slide_20 = get(client, f"/api/competitions/{cid}/slides/{sid_at_order}", headers=h)
+    response, order_body = get(client, f"/api/competitions/{cid}/slides/{order}", headers=h)
     assert response.status_code == codes.OK
 
     # Changes order
-    response, _ = put(
-        client, f"/api/competitions/{cid}/slides/{sid}/order", {"order": order}, headers=h
-    )  # uses order to be able to test negative order
+    response, _ = put(client, f"/api/competitions/{cid}/slides/{order}/order", {"order": new_order}, headers=h)
     assert response.status_code == codes.OK
-
-    # Gets new versions of slides
-    response, item_slide_11 = get(client, f"/api/competitions/{cid}/slides/{sid}", headers=h)
-    assert response.status_code == codes.OK
-    response, item_slide_21 = get(client, f"/api/competitions/{cid}/slides/{sid_at_order}", headers=h)
-    assert response.status_code == codes.OK
-
-    # Checks that the order was indeed swapped
-    assert item_slide_10["order"] == item_slide_21["order"]
-    assert item_slide_11["order"] == item_slide_20["order"]
-- 
GitLab