Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • tddd96-grupp11/teknikattan-scoring-system
1 result
Show changes
Commits on Source (5)
Showing
with 371 additions and 64 deletions
......@@ -10,10 +10,17 @@ import { City } from '../../../interfaces/ApiModels'
import { AddCompetitionModel, FormModel } from '../../../interfaces/FormModels'
import { AddButton, AddContent, AddForm } from '../styledComp'
/**
* Component description:
* This component handles the functionality when adding a competition to the system
* This component is a child component to CompetitionManager.tsx
*/
type formType = FormModel<AddCompetitionModel>
const noCitySelected = 'Välj stad'
//Description of the form and what is required
const competitionSchema: Yup.SchemaOf<formType> = Yup.object({
model: Yup.object()
.shape({
......@@ -45,20 +52,25 @@ const AddCompetition: React.FC = (props: any) => {
const dispatch = useAppDispatch()
const id = open ? 'simple-popover' : undefined
const currentYear = new Date().getFullYear()
// Handles the actual submition to the database
const handleCompetitionSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
// The parameters sent
const params = {
name: values.model.name,
year: values.model.year,
city_id: selectedCity?.id as number,
}
await axios
.post('/competitions', params)
.post('/competitions', params) // send to database
.then(() => {
actions.resetForm()
actions.resetForm() // reset the form
setAnchorEl(null)
dispatch(getCompetitions())
dispatch(getCompetitions()) // refresh competitions
setSelectedCity(undefined)
})
// if the post request fails
.catch(({ response }) => {
console.warn(response.data)
if (response.data && response.data.message)
......@@ -83,6 +95,12 @@ const AddCompetition: React.FC = (props: any) => {
>
Ny Tävling
</AddButton>
{/**
* The "pop up" menu for adding a competition
* contains 3 fields; Name, Region and Year
*
*/}
<Popover
id={id}
open={open}
......
......@@ -21,6 +21,13 @@ import { CompetitionFilterParams } from '../../../interfaces/FilterParams'
import { FilterContainer, RemoveMenuItem, TopBar, YearFilterTextField } from '../styledComp'
import AddCompetition from './AddCompetition'
/**
* Component description:
* This component shows a list of all the competitions which a user can search through
* We can also start, duplicate or delete a competition
*/
// Use defined styling
const useStyles = makeStyles((theme: Theme) =>
createStyles({
table: {
......@@ -41,6 +48,7 @@ const CompetitionManager: React.FC = (props: any) => {
const filterParams = useAppSelector((state) => state.competitions.filterParams)
const competitionTotal = useAppSelector((state) => state.competitions.total)
const cities = useAppSelector((state) => state.cities.cities)
const classes = useStyles()
const noFilterText = 'Alla'
const dispatch = useAppDispatch()
......@@ -59,6 +67,7 @@ const CompetitionManager: React.FC = (props: any) => {
dispatch(getCompetitions())
}, [])
// Searchfuntion to search for a specific string
const onSearchChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
if (timerHandle) {
clearTimeout(timerHandle)
......@@ -69,13 +78,14 @@ const CompetitionManager: React.FC = (props: any) => {
dispatch(setFilterParams({ ...filterParams, name: event.target.value }))
}
// Function to remove a competition from the systems database
const handleDeleteCompetition = async () => {
if (activeId) {
await axios
.delete(`/competitions/${activeId}`)
.then(() => {
setAnchorEl(null)
dispatch(getCompetitions())
dispatch(getCompetitions()) // refresh the competition list
})
.catch(({ response }) => {
console.warn(response.data)
......@@ -83,6 +93,11 @@ const CompetitionManager: React.FC = (props: any) => {
}
}
const handleStartCompetition = () => {
history.push(`/presenter/id=${activeId}&code=123123`)
console.log('GLHF!')
}
const handleDuplicateCompetition = async () => {
if (activeId) {
await axios
......@@ -177,6 +192,7 @@ const CompetitionManager: React.FC = (props: any) => {
))}
</TableBody>
</Table>
{/** We can't find any competitions at all or with a specific filter */}
{(!competitions || competitions.length === 0) && (
<Typography>Inga tävlingar hittades med nuvarande filter</Typography>
)}
......@@ -190,7 +206,7 @@ const CompetitionManager: React.FC = (props: any) => {
onChangePage={(event, newPage) => handleFilterChange({ ...filterParams, page: newPage })}
/>
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={() => history.push(`/presenter/id=${activeId}&code=123123`)}>Starta</MenuItem>
<MenuItem onClick={handleStartCompetition}>Starta</MenuItem>
<MenuItem onClick={handleDuplicateCompetition}>Duplicera</MenuItem>
<RemoveMenuItem onClick={handleDeleteCompetition}>Ta bort</RemoveMenuItem>
</Menu>
......
import {
Button,
createStyles,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
FormControl,
InputLabel,
makeStyles,
......@@ -8,6 +13,8 @@ import {
Popover,
TextField,
Theme,
useMediaQuery,
useTheme,
} from '@material-ui/core'
import MoreHorizIcon from '@material-ui/icons/MoreHoriz'
import { Alert, AlertTitle } from '@material-ui/lab'
......@@ -62,6 +69,11 @@ type UserIdProps = {
}
const EditUser = ({ user }: UserIdProps) => {
// for dialog alert
const [openAlert, setOpen] = React.useState(false)
const theme = useTheme()
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
const dispatch = useAppDispatch()
const classes = useStyles()
......@@ -87,21 +99,25 @@ const EditUser = ({ user }: UserIdProps) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setOpen(false)
setAnchorEl(null)
}
const handleVerifyDelete = () => {
setOpen(true)
}
const handleDeleteUsers = async () => {
if (confirm('Are u sure?')) {
await axios
.delete(`/auth/delete/${user.id}`)
.then(() => {
setAnchorEl(null)
dispatch(getSearchUsers())
})
.catch(({ response }) => {
console.warn(response.data)
})
}
setOpen(false)
await axios
.delete(`/auth/delete/${user.id}`)
.then(() => {
setAnchorEl(null)
dispatch(getSearchUsers())
})
.catch(({ response }) => {
console.warn(response.data)
})
}
const handleSubmit = async (values: formType, actions: FormikHelpers<formType>) => {
......@@ -273,7 +289,7 @@ const EditUser = ({ user }: UserIdProps) => {
Ändra
</Button>
<Button
onClick={handleDeleteUsers}
onClick={handleVerifyDelete}
className={classes.deleteButton}
fullWidth
variant="contained"
......@@ -281,6 +297,27 @@ const EditUser = ({ user }: UserIdProps) => {
>
Ta bort
</Button>
<Dialog
fullScreen={fullScreen}
open={openAlert}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">{'Ta bort användare?'}</DialogTitle>
<DialogContent>
<DialogContentText>
Är du säker på att du vill ta bort användaren och all dess information från systemet?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
Avbryt
</Button>
<Button onClick={handleDeleteUsers} color="primary" autoFocus>
Ta bort
</Button>
</DialogActions>
</Dialog>
{formik.errors.error && (
<Alert severity="error">
......
import React from 'react';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { useTheme } from '@material-ui/core/styles';
export default function ResponsiveDialog() {
const [open, setOpen] = React.useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open responsive dialog
</Button>
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">{"Use Google's location service?"}</DialogTitle>
<DialogContent>
<DialogContentText>
Let Google help apps determine location. This means sending anonymous location data to
Google, even when no apps are running.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
Disagree
</Button>
<Button onClick={handleClose} color="primary" autoFocus>
Agree
</Button>
</DialogActions>
</Dialog>
</div>
);
}
\ No newline at end of file
import React from 'react'
import SlideDisplay from './components/SlideDisplay'
const AudienceViewPage: React.FC = () => {
return <div>Publik</div>
return <SlideDisplay />
}
export default AudienceViewPage
import { Divider, List, ListItemText } from '@material-ui/core'
import { Divider, List, ListItemText, Typography } from '@material-ui/core'
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
......@@ -10,6 +10,7 @@ import {
} from '../../actions/presentation'
import { useAppDispatch, useAppSelector } from '../../hooks'
import { ViewParams } from '../../interfaces/ViewParams'
import { socket_connect } from '../../sockets'
import { SlideListItem } from '../presentationEditor/styled'
import JudgeScoreDisplay from './components/JudgeScoreDisplay'
import SlideDisplay from './components/SlideDisplay'
......@@ -43,17 +44,20 @@ const JudgeViewPage: React.FC = () => {
const { id, code }: ViewParams = useParams()
const dispatch = useAppDispatch()
const [activeSlideIndex, setActiveSlideIndex] = useState<number>(0)
useEffect(() => {
dispatch(getPresentationCompetition(id))
dispatch(getPresentationTeams(id))
dispatch(setPresentationCode(code))
}, [])
const teams = useAppSelector((state) => state.presentation.teams)
const slides = useAppSelector((state) => state.presentation.competition.slides)
const handleSelectSlide = (index: number) => {
setActiveSlideIndex(index)
dispatch(setCurrentSlide(slides[index]))
}
useEffect(() => {
socket_connect()
dispatch(getPresentationCompetition(id))
dispatch(getPresentationTeams(id))
dispatch(setPresentationCode(code))
}, [])
return (
<div>
<JudgeAppBar position="fixed">
......@@ -80,6 +84,7 @@ const JudgeViewPage: React.FC = () => {
button
key={slide.id}
>
<Typography variant="h6">Slide ID: {slide.id} </Typography>
<ListItemText primary={slide.title} />
</SlideListItem>
))}
......@@ -103,7 +108,6 @@ const JudgeViewPage: React.FC = () => {
))}
</List>
</RightDrawer>
aaa
<Content leftDrawerWidth={leftDrawerWidth} rightDrawerWidth={rightDrawerWidth}>
<div className={classes.toolbar} />
<SlideDisplay />
......
import { List, ListItem, Popover } from '@material-ui/core'
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
List,
ListItem,
Popover,
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from '@material-ui/core'
import AssignmentIcon from '@material-ui/icons/Assignment'
import BackspaceIcon from '@material-ui/icons/Backspace'
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'
import ChevronRightIcon from '@material-ui/icons/ChevronRight'
import TimerIcon from '@material-ui/icons/Timer'
import React, { useEffect } from 'react'
import { useHistory, useParams } from 'react-router-dom'
import { getPresentationCompetition, getPresentationTeams, setPresentationCode } from '../../actions/presentation'
import { useAppDispatch, useAppSelector } from '../../hooks'
import { ViewParams } from '../../interfaces/ViewParams'
import {
socketEndPresentation,
socketSetSlide,
socketSetSlideNext,
socketSetSlidePrev,
socketStartPresentation,
socketStartTimer,
socket_connect,
} from '../../sockets'
import SlideDisplay from './components/SlideDisplay'
import SocketTest from './components/SocketTest'
import Timer from './components/Timer'
import { PresenterButton, PresenterContainer, PresenterFooter, PresenterHeader } from './styled'
import {
PresenterButton,
PresenterContainer,
PresenterFooter,
PresenterHeader,
SlideCounter,
ToolBarContainer,
} from './styled'
/**
* Presentation is an active competition
*/
const PresenterViewPage: React.FC = () => {
// for dialog alert
const [openAlert, setOpen] = React.useState(false)
const theme = useTheme()
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'))
const teams = useAppSelector((state) => state.presentation.teams)
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
const { id, code }: ViewParams = useParams()
const presentation = useAppSelector((state) => state.presentation)
const history = useHistory()
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(getPresentationCompetition(id))
dispatch(getPresentationTeams(id))
dispatch(setPresentationCode(code))
socket_connect()
socketSetSlide // Behövs denna?
setTimeout(startCompetition, 500) // Ghetto, wait for everything to load
console.log(id)
}, [])
const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setOpen(false)
setAnchorEl(null)
}
const handleNextSlidePressed = () => {
// dispatch(setCurrentSlideNext())
// syncSlide()
const startCompetition = () => {
socketStartPresentation()
console.log('started competition for')
console.log(id)
}
const handleVerifyExit = () => {
setOpen(true)
}
const handlePreviousSlidePressed = () => {
// dispatch(setCurrentSlidePrevious())
// syncSlide()
const endCompetition = () => {
setOpen(false)
socketEndPresentation()
history.push('/admin/tävlingshanterare')
window.location.reload(false) // TODO: fix this ugly hack, we "need" to refresh site to be able to run the competition correctly again
}
return (
<PresenterContainer>
<PresenterHeader>
<PresenterButton onClick={handleOpenPopover} color="primary" variant="contained">
Visa ställning
</PresenterButton>
<PresenterButton onClick={() => history.push('/admin')} variant="contained" color="secondary">
Avsluta tävling
</PresenterButton>
<Tooltip title="Avsluta tävling" arrow>
<PresenterButton onClick={handleVerifyExit} variant="contained" color="secondary">
<BackspaceIcon fontSize="large" />
</PresenterButton>
</Tooltip>
<Dialog
fullScreen={fullScreen}
open={openAlert}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
>
<DialogTitle id="responsive-dialog-title">{'Vill du avsluta tävlingen?'}</DialogTitle>
<DialogContent>
<DialogContentText>
Genom att avsluta tävlingen kommer den avslutas för alla. Du kommer gå tillbaka till startsidan.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleClose} color="primary">
Avbryt
</Button>
<Button onClick={endCompetition} color="primary" autoFocus>
Avsluta tävling
</Button>
</DialogActions>
</Dialog>
<Typography variant="h3">{presentation.competition.name}</Typography>
<SlideCounter>
<Typography variant="h3">
{presentation.slide.order + 1} / {presentation.competition.slides.length}
</Typography>
</SlideCounter>
</PresenterHeader>
<SlideDisplay />
<PresenterFooter>
<PresenterButton onClick={handlePreviousSlidePressed} variant="contained">
<ChevronRightIcon fontSize="large" />
</PresenterButton>
<SocketTest></SocketTest>
<Timer></Timer>
<PresenterButton onClick={handleNextSlidePressed} variant="contained">
<ChevronRightIcon fontSize="large" />
</PresenterButton>
<ToolBarContainer>
<Tooltip title="Previous Slide" arrow>
<PresenterButton onClick={socketSetSlidePrev} variant="contained">
<ChevronLeftIcon fontSize="large" />
</PresenterButton>
</Tooltip>
{/*
// Manual start button
<Tooltip title="Start Presentation" arrow>
<PresenterButton onClick={startCompetition} variant="contained">
start
</PresenterButton>
</Tooltip>
// This creates a join button, but presenter should not join others, others should join presenter
<Tooltip title="Join Presentation" arrow>
<PresenterButton onClick={socketJoinPresentation} variant="contained">
<GroupAddIcon fontSize="large" />
</PresenterButton>
</Tooltip>
// This creates another end button, it might not be needed since we already have one
<Tooltip title="End Presentation" arrow>
<PresenterButton onClick={socketEndPresentation} variant="contained">
<CancelIcon fontSize="large" />
</PresenterButton>
</Tooltip>
*/}
<Tooltip title="Start Timer" arrow>
<PresenterButton onClick={socketStartTimer} variant="contained">
<TimerIcon fontSize="large" />
<Timer></Timer>
</PresenterButton>
</Tooltip>
<Tooltip title="Scoreboard" arrow>
<PresenterButton onClick={handleOpenPopover} variant="contained">
<AssignmentIcon fontSize="large" />
</PresenterButton>
</Tooltip>
<Tooltip title="Next Slide" arrow>
<PresenterButton onClick={socketSetSlideNext} variant="contained">
<ChevronRightIcon fontSize="large" />
</PresenterButton>
</Tooltip>
</ToolBarContainer>
</PresenterFooter>
<Popover
open={Boolean(anchorEl)}
......@@ -71,6 +201,9 @@ const PresenterViewPage: React.FC = () => {
}}
>
<List>
{/** TODO:
* Fix scoreboard
*/}
{teams.map((team) => (
<ListItem key={team.id}>{team.name} score: 20</ListItem>
))}
......@@ -81,3 +214,6 @@ const PresenterViewPage: React.FC = () => {
}
export default PresenterViewPage
function componentDidMount() {
throw new Error('Function not implemented.')
}
......@@ -5,10 +5,15 @@ import { SlideContainer } from './styled'
const SlideDisplay: React.FC = () => {
const currentSlide = useAppSelector((state) => state.presentation.slide)
return (
<SlideContainer>
<Typography variant="h3">{currentSlide.title}</Typography>
</SlideContainer>
<div>
<SlideContainer>
<Typography variant="h3">Slide Title: {currentSlide.title} </Typography>
<Typography variant="h3">Timer: {currentSlide.timer} </Typography>
<Typography variant="h3">Slide ID: {currentSlide.id} </Typography>
</SlideContainer>
</div>
)
}
......
......@@ -38,8 +38,7 @@ const Timer: React.FC = (props: any) => {
return (
<>
<div>Timer: {props.timer.value}</div>
<div>Enabled: {props.timer.enabled.toString()}</div>
<div>{props.timer.value}</div>
</>
)
}
......
......@@ -3,7 +3,14 @@ import styled from 'styled-components'
export const SlideContainer = styled.div`
display: flex;
flex-direction: column;
margin-left: auto;
margin-right: auto;
margin-top: 5%;
justify-content: center;
background-color: grey;
width: 1280px;
height: 720px;
`
export const ScoreDisplayContainer = styled.div`
......
......@@ -50,8 +50,15 @@ export const PresenterFooter = styled.div`
export const PresenterButton = styled(Button)`
width: 100px;
height: 100px;
padding-top: 16px;
padding-bottom: 16px;
margin-left: 16px;
margin-right: 16px;
margin-top: 16px;
`
export const SlideCounter = styled(Button)`
margin-left: 16px;
margin-right: 16px;
margin-top: 16px;
`
export const PresenterContainer = styled.div`
......@@ -61,6 +68,18 @@ export const PresenterContainer = styled.div`
height: 100%;
`
export const ToolBarContainer = styled.div`
align-self: center;
display: flex;
flex-direction: row;
justify-content: space-between;
height: 100%;
width: auto;
margin-right: auto;
margin-left: auto;
margin-bottom: 20px;
`
interface DrawerProps {
width: number
}
......
......@@ -38,22 +38,27 @@ export const socket_connect = () => {
export const socketStartPresentation = () => {
socket.emit('start_presentation', { competition_id: store.getState().presentation.competition.id })
console.log('START PRESENTATION')
}
export const socketJoinPresentation = () => {
socket.emit('join_presentation', { code: 'OEM1V4' }) // TODO: Send code gotten from auth/login/<code> api call
socket.emit('join_presentation', { code: 'CO0ART' }) // TODO: Send code gotten from auth/login/<code> api call
console.log('JOIN PRESENTATION')
}
export const socketEndPresentation = () => {
socket.emit('end_presentation', { competition_id: store.getState().presentation.competition.id })
console.log('END PRESENTATION')
}
export const socketSetSlideNext = () => {
socketSetSlide(store.getState().presentation.slide.order + 1) // TODO: Check that this slide exists
console.log('NEXT SLIDE +1')
}
export const socketSetSlidePrev = () => {
socketSetSlide(store.getState().presentation.slide.order - 1) // TODO: Check that this slide exists
console.log('PREVIOUS SLIDE -1')
}
export const socketSetSlide = (slide_order: number) => {
......@@ -69,6 +74,7 @@ export const socketSetSlide = (slide_order: number) => {
}
export const socketSetTimer = (timer: Timer) => {
console.log('SET TIMER')
socket.emit('set_timer', {
competition_id: store.getState().presentation.competition.id,
timer: timer,
......@@ -76,5 +82,6 @@ export const socketSetTimer = (timer: Timer) => {
}
export const socketStartTimer = () => {
console.log('START TIMER')
socketSetTimer({ enabled: true, value: store.getState().presentation.timer.value })
}
......@@ -37,7 +37,7 @@ class QuestionAlternatives(Resource):
def put(self, CID, SOrder, QID, AID):
args = question_alternative_parser.parse_args(strict=True)
item = dbc.get.one(QuestionAlternative, AID)
item = dbc.edit.question_alternative(item, **args)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
@check_jwt(editor=True)
......
......@@ -37,5 +37,5 @@ class QuestionAnswers(Resource):
def put(self, CID, TID, AID):
args = question_answer_edit_parser.parse_args(strict=True)
item = dbc.get.one(QuestionAnswer, AID)
item = dbc.edit.question_answer(item, **args)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
......@@ -42,7 +42,7 @@ class Competitions(Resource):
def put(self, CID):
args = competition_parser.parse_args(strict=True)
item = dbc.get.one(Competition, CID)
item = dbc.edit.competition(item, **args)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
......
......@@ -25,7 +25,7 @@ class ComponentByID(Resource):
def put(self, CID, SOrder, component_id):
args = component_parser.parse_args()
item = dbc.get.one(Component, component_id)
item = dbc.edit.component(item, **args)
item = dbc.edit.default(item, **args)
return item_response(schema.dump(item))
@check_jwt(editor=True)
......
......@@ -48,7 +48,7 @@ class QuestionById(Resource):
args = question_parser.parse_args(strict=True)
item_question = dbc.get.question(CID, SID, QID)
item_question = dbc.edit.question(item_question, **args)
item_question = dbc.edit.default(item_question, **args)
return item_response(schema.dump(item_question))
......
......@@ -44,7 +44,7 @@ class Slides(Resource):
timer = args.get("timer")
item_slide = dbc.get.slide(CID, SOrder)
item_slide = dbc.edit.slide(item_slide, title, timer)
item_slide = dbc.edit.default(item_slide, title=title, timer=timer)
return item_response(schema.dump(item_slide))
......
......@@ -53,5 +53,5 @@ class Teams(Resource):
item_team = dbc.get.team(CID, TID)
item_team = dbc.edit.team(item_team, name=name, competition_id=CID)
item_team = dbc.edit.default(item_team, name=name, competition_id=CID)
return item_response(schema.dump(item_team))
......@@ -19,7 +19,12 @@ def edit_user(item_user, args):
if User.query.filter(User.email == args["email"]).count() > 0:
api.abort(codes.BAD_REQUEST, "Email is already in use")
return dbc.edit.user(item_user, **args)
try:
args["name"] = args.get("name").title()
except Exception:
pass
return dbc.edit.default(item_user, **args)
@api.route("/")
......