error checking all over

This commit is contained in:
Elijah Steres 2021-01-16 01:13:12 -05:00
parent e184c91515
commit 49027b37c1
4 changed files with 175 additions and 72 deletions

View File

@ -38,34 +38,69 @@ def search_teams():
return jsonify([json.loads(x[0])['name'] for x in result]) #currently all we need is the name but that can change return jsonify([json.loads(x[0])['name'] for x in result]) #currently all we need is the name but that can change
MAX_SUBLEAGUE_DIVISION_TOTAL = 22;
MAX_TEAMS_PER_DIVISION = 12;
@app.route('/api/leagues', methods=['POST']) @app.route('/api/leagues', methods=['POST'])
def create_league(): def create_league():
config = json.loads(request.data) config = json.loads(request.data)
if (league_exists(config['name'])): if league_exists(config['name']):
abort(400, "A league by that name already exists") return jsonify({'status':'err_league_exists'}), 400
print(config) num_subleagues = len(config['structure']['subleagues'])
league_dic = { if num_subleagues < 1 or num_subleagues % 2 != 0:
subleague['name'] : { return jsonify({'status':'err_invalid_subleague_count'}), 400
division['name'] : [games.get_team(team_name) for team_name in division['teams']]
for division in subleague['divisions'] num_divisions = len(config['structure']['subleagues'][0]['divisions'])
} if num_subleagues * (num_divisions + 1) > MAX_SUBLEAGUE_DIVISION_TOTAL:
for subleague in config['structure']['subleagues'] return jsonify({'status':'err_invalid_subleague_division_total'}), 400
}
league_dic = {}
err_teams = []
for subleague in config['structure']['subleagues']:
if subleague['name'] in league_dic:
return jsonify({'status':'err_duplicate_name', 'cause':subleague['name']})
subleague_dic = {}
for division in subleague['divisions']:
if division['name'] in subleague_dic:
return jsonify({'status':'err_duplicate_name', 'cause':f"{subleague['name']}/{division['name']}"}), 400
elif len(division['teams']) > MAX_TEAMS_PER_DIVISION:
return jsonify({'status':'err_too_many_teams', 'cause':f"{subleague['name']}/{division['name']}"})
teams = []
for team_name in division['teams']:
team = games.get_team(team_name)
if team is None:
err_teams.append(team_name)
else:
teams.append(team)
subleague_dic[division['name']] = teams
league_dic[subleague['name']] = subleague_dic
if len(err_teams) > 0:
return jsonify({'status':'err_no_such_team', 'cause': err_teams}), 400
for (key, min_val) in [
('division_series', 1),
('inter_division_series', 1),
('inter_league_series', 1)
]:
if config[key] < min_val:
return jsonify({'status':'err_invalid_optiion_value', 'cause':key}), 400
new_league = league_structure(config['name']) new_league = league_structure(config['name'])
new_league.setup( new_league.setup(
league_dic, league_dic,
division_games=config['division_series'], division_games=config['division_series'], # need to add a check that makes sure these values are ok
inter_division_games=config['inter_division_series'], inter_division_games=config['inter_division_series'],
inter_league_games=config['inter_league_series'], inter_league_games=config['inter_league_series'],
) )
new_league.generate_schedule() new_league.generate_schedule()
leagues.save_league(new_league) leagues.save_league(new_league)
return "League created successfully" return jsonify({'status':'success_league_created'})

View File

@ -240,7 +240,7 @@ input[type=number]::-webkit-outer-spin-button {
.cl_option_err { .cl_option_err {
min-height: 1.5rem; min-height: 1.5rem;
margin-bottom: -0.5rem; margin-bottom: -0.5rem;
margin-top: 0.5rem; margin-top: 0.25rem;
} }
.cl_structure_err { .cl_structure_err {
@ -248,14 +248,24 @@ input[type=number]::-webkit-outer-spin-button {
} }
.cl_structure_err_div { .cl_structure_err_div {
margin-top: -0.5rem; margin-top: -0.25rem;
margin-bottom: 0; margin-bottom: 0;
} }
.cl_Structure_err_team {
margin-top: -0.5rem;
width: 85%;
}
.cl_structure_err_teams { .cl_structure_err_teams {
width: 98%; width: 98%;
} }
.cl_subleague_add_filler, .cl_division_add_filler {
width: 2rem;
height: 2rem;
}
/* button styles */ /* button styles */
button > .emoji { button > .emoji {
@ -283,12 +293,12 @@ button > .emoji {
background: var(--accent-green); background: var(--accent-green);
} }
.cl_subleague_add { .cl_subleague_add, .cl_subleague_add_filler {
position: relative; position: relative;
top: 1.6rem; top: 1.5rem;
} }
.cl_division_add { .cl_division_add, .cl_division_add_filler {
margin-top: 1.25rem; margin-top: 1.25rem;
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
} }

View File

@ -3,6 +3,11 @@ import {removeIndex, replaceIndex, append, arrayOf, shallowClone, getUID, Distri
import './CreateLeague.css'; import './CreateLeague.css';
import twemoji from 'twemoji'; import twemoji from 'twemoji';
// CONSTS
const MAX_SUBLEAGUE_DIVISION_TOTAL = 22;
const MAX_TEAMS_PER_DIVISION = 12;
// STATE CLASSES // STATE CLASSES
class LeagueStructureState { class LeagueStructureState {
@ -178,6 +183,7 @@ function CreateLeague() {
let [name, setName] = useState(""); let [name, setName] = useState("");
let [showError, setShowError] = useState(false); let [showError, setShowError] = useState(false);
let [nameExists, setNameExists] = useState(false); let [nameExists, setNameExists] = useState(false);
let [deletedTeams, setDeletedTeams] = useState<string[]>([]);
let [createSuccess, setCreateSuccess] = useState(false); let [createSuccess, setCreateSuccess] = useState(false);
let [structure, structureDispatch] = useReducer(leagueStructureReducer, initLeagueStructure); let [structure, structureDispatch] = useReducer(leagueStructureReducer, initLeagueStructure);
let [options, optionsDispatch] = useReducer(LeagueOptionsReducer, new LeagueOptionsState()); let [options, optionsDispatch] = useReducer(LeagueOptionsReducer, new LeagueOptionsState());
@ -211,7 +217,7 @@ function CreateLeague() {
nameExists && showError ? "A league by that name already exists" : nameExists && showError ? "A league by that name already exists" :
"" ""
}</div> }</div>
<LeagueStructre state={structure} dispatch={structureDispatch} showError={showError}/> <LeagueStructre state={structure} dispatch={structureDispatch} deletedTeams={deletedTeams} showError={showError}/>
<div className="cl_league_options"> <div className="cl_league_options">
<LeagueOptions state={options} dispatch={optionsDispatch} showError={showError}/> <LeagueOptions state={options} dispatch={optionsDispatch} showError={showError}/>
<div className="cl_option_submit_box"> <div className="cl_option_submit_box">
@ -229,13 +235,20 @@ function CreateLeague() {
setCreateSuccess(true); setCreateSuccess(true);
} }
if (req.status === 400) { if (req.status === 400) {
setNameExists(true); let err = JSON.parse(req.response);
switch (err.status) {
case 'err_league_exists':
setNameExists(true);
break;
case 'err_no_such_team':
setDeletedTeams(err.cause);
break;
}
setShowError(true); setShowError(true);
} }
} }
} }
req.send(data); req.send(data);
} }
}}>Submit</button> }}>Submit</button>
<div className="cl_option_err">{ <div className="cl_option_err">{
@ -270,45 +283,61 @@ function makeRequest(name:string, structure: LeagueStructureState, options:Leagu
} }
function validRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) { function validRequest(name:string, structure: LeagueStructureState, options:LeagueOptionsState) {
return ( return (
name !== "" && name !== "" &&
validNumber(options.games_series) && validNumber(options.games_series) &&
validNumber(options.intra_division_series) && validNumber(options.intra_division_series) &&
validNumber(options.inter_division_series) && validNumber(options.inter_division_series) &&
validNumber(options.inter_league_series) && validNumber(options.inter_league_series) &&
validNumber(options.top_postseason) && validNumber(options.top_postseason) &&
validNumber(options.wildcards, 0) && validNumber(options.wildcards, 0) &&
structure.subleagues.length % 2 === 0 && structure.subleagues.length % 2 === 0 &&
structure.subleagues.every(subleague =>
structure.subleagues.every((subleague, si) =>
subleague.name !== "" && subleague.name !== "" &&
subleague.divisions.every(division => !structure.subleagues.slice(0, si).some(val => val.name === subleague.name) &&
subleague.divisions.every((division, di) =>
division.name !== "" && division.name !== "" &&
division.teams.length >= 2 division.teams.length >= 2 &&
division.teams.length <= MAX_TEAMS_PER_DIVISION &&
!subleague.divisions.slice(0, di).some(val => val.name === division.name)
) )
) )
) )
} }
function validNumber(value: string, min = 1) { function validNumber(value: string, min = 1) {
return !isNaN(Number(value)) && Number(value) >= min return !isNaN(Number(value)) && Number(value) >= min;
} }
// LEAGUE STRUCUTRE // LEAGUE STRUCUTRE
function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dispatch<StructureReducerActions>, showError: boolean}) { function LeagueStructre(props: {state: LeagueStructureState, dispatch: React.Dispatch<StructureReducerActions>, deletedTeams: string[], showError: boolean}) {
let nSubleagues = props.state.subleagues.length;
let nDivisions = props.state.subleagues[0].divisions.length;
return ( return (
<div className="cl_league_structure"> <div className="cl_league_structure">
<div className="cl_league_structure_scrollbox"> <div className="cl_league_structure_scrollbox">
<div className="cl_subleague_add_align"> <div className="cl_subleague_add_align">
<div className="cl_league_structure_table"> <div className="cl_league_structure_table">
<SubleagueHeaders subleagues={props.state.subleagues} dispatch={props.dispatch} showError={props.showError}/> <SubleagueHeaders subleagues={props.state.subleagues} dispatch={props.dispatch} showError={props.showError}/>
<Divisions subleagues={props.state.subleagues} dispatch={props.dispatch} showError={props.showError}/> <Divisions subleagues={props.state.subleagues} dispatch={props.dispatch} deletedTeams={props.deletedTeams} showError={props.showError}/>
</div> </div>
<button className="cl_subleague_add" onClick={e => props.dispatch({type: 'add_subleague'})}></button> { (nSubleagues+1) * (nDivisions+1) < MAX_SUBLEAGUE_DIVISION_TOTAL ?
<button className="cl_subleague_add" onClick={e => props.dispatch({type: 'add_subleague'})}></button> :
<div className="cl_subleague_add_filler"/>
}
</div> </div>
</div> </div>
<div className="cl_structure_err">{props.state.subleagues.length % 2 !== 0 && props.showError ? "Must have an even number of subleagues." : ""}</div> <div className="cl_structure_err">{props.state.subleagues.length % 2 !== 0 && props.showError ? "Must have an even number of subleagues." : ""}</div>
<button className="cl_division_add" onClick={e => props.dispatch({type: 'add_divisions'})}></button> { nSubleagues * (nDivisions+2) < MAX_SUBLEAGUE_DIVISION_TOTAL ?
<button className="cl_division_add" onClick={e => props.dispatch({type: 'add_divisions'})}></button>:
<div className="cl_division_add_filler"/>
}
</div> </div>
); );
} }
@ -317,16 +346,25 @@ function SubleagueHeaders(props: {subleagues: SubleagueState[], dispatch: React.
return ( return (
<div className="cl_headers"> <div className="cl_headers">
<div key="filler" className="cl_delete_filler"/> <div key="filler" className="cl_delete_filler"/>
{props.subleagues.map((subleague, i) => ( {props.subleagues.map((subleague, i) => {
<div key={subleague.id} className="cl_table_header"> let err =
<div className="cl_subleague_bg"> subleague.name === "" ?
<SubleageHeader state={subleague} canDelete={props.subleagues.length > 1} dispatch={action => "A name is required." :
props.dispatch(Object.assign({subleague_index: i}, action)) props.subleagues.slice(0, i).some(val => val.name === subleague.name) ?
}/> "Each subleague must have a different name." :
<div className="cl_structure_err">{subleague.name === "" && props.showError ? "A name is required." : ""}</div> "";
return (
<div key={subleague.id} className="cl_table_header">
<div className="cl_subleague_bg">
<SubleageHeader state={subleague} canDelete={props.subleagues.length > 1} dispatch={action =>
props.dispatch(Object.assign({subleague_index: i}, action))
}/>
<div className="cl_structure_err">{props.showError ? err : ""}</div>
</div>
</div> </div>
</div> )
))} })}
</div> </div>
); );
} }
@ -342,7 +380,7 @@ function SubleageHeader(props: {state: SubleagueState, canDelete: boolean, dispa
); );
} }
function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<StructureReducerActions>, showError: boolean}) { function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatch<StructureReducerActions>, deletedTeams: string[], showError: boolean}) {
return (<> return (<>
{props.subleagues[0].divisions.map((val, di) => ( {props.subleagues[0].divisions.map((val, di) => (
<div key={val.id} className="cl_table_row"> <div key={val.id} className="cl_table_row">
@ -357,7 +395,9 @@ function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatc
<div className="cl_subleague_bg"> <div className="cl_subleague_bg">
<Division state={subleague.divisions[di]} dispatch={action => <Division state={subleague.divisions[di]} dispatch={action =>
props.dispatch(Object.assign({subleague_index: si, division_index: di}, action)) props.dispatch(Object.assign({subleague_index: si, division_index: di}, action))
} showError={props.showError}/> }
isDuplicate={subleague.divisions.slice(0, di).some(val => val.name === subleague.divisions[di].name)}
deletedTeams={props.deletedTeams} showError={props.showError} />
</div> </div>
</div> </div>
))} ))}
@ -366,7 +406,14 @@ function Divisions(props: {subleagues: SubleagueState[], dispatch: React.Dispatc
</>); </>);
} }
function Division(props: {state: DivisionState, dispatch:(action: DistributiveOmit<StructureReducerActions, 'subleague_index'|'division_index'>) => void, showError:boolean}) { function Division(props: {
state: DivisionState,
dispatch: (action: DistributiveOmit<StructureReducerActions, 'subleague_index'|'division_index'>) => void,
isDuplicate: boolean,
deletedTeams: string[],
showError: boolean
}) {
let [newName, setNewName] = useState(""); let [newName, setNewName] = useState("");
let [searchResults, setSearchResults] = useState<string[]>([]); let [searchResults, setSearchResults] = useState<string[]>([]);
let newNameInput = useRef<HTMLInputElement>(null); let newNameInput = useRef<HTMLInputElement>(null);
@ -378,45 +425,59 @@ function Division(props: {state: DivisionState, dispatch:(action: DistributiveOm
} }
}) })
let divisionErr =
props.state.name === "" ?
"A name is required." :
props.isDuplicate ?
"Each division in a subleague must have a different name." :
""
let teamsErr = props.state.teams.length < 2 ? "Must have at least 2 teams." : "";
return ( return (
<div className="cl_division"> <div className="cl_division">
<div className="cl_division_name_box"> <div className="cl_division_name_box">
<input type="text" className="cl_division_name" placeholder="Division Name" key="input" value={props.state.name} onChange={e => <input type="text" className="cl_division_name" placeholder="Division Name" key="input" value={props.state.name} onChange={e =>
props.dispatch({type: 'rename_division', name: e.target.value}) props.dispatch({type: 'rename_division', name: e.target.value})
}/> }/>
<div className="cl_structure_err cl_structure_err_div">{props.state.name === "" && props.showError ? "A name is required." : ""}</div> <div className="cl_structure_err cl_structure_err_div">{props.showError ? divisionErr : ""}</div>
</div> </div>
{props.state.teams.map((team, i) => ( {props.state.teams.map((team, i) => (<>
<div className="cl_team" key={team.id}> <div className="cl_team" key={team.id}>
<div className="cl_team_name">{team.name}</div> <div className="cl_team_name">{team.name}</div>
<button className="cl_team_delete" onClick={e => props.dispatch({type:'remove_team', name: team.name})}></button> <button className="cl_team_delete" onClick={e => props.dispatch({type:'remove_team', name: team.name})}></button>
</div> </div>
))} <div className="cl_structure_err cl_structure_err_team">{props.showError && props.deletedTeams.includes(team.name) ? "This team was deleted" : ""}</div>
<div className="cl_team_add"> </>))}
<input type="text" className="cl_newteam_name" placeholder="Add team..." value={newName} ref={newNameInput} { props.state.teams.length < MAX_TEAMS_PER_DIVISION ? <>
onChange={e => { <div className="cl_team_add">
let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'}); <input type="text" className="cl_newteam_name" placeholder="Add team..." value={newName} ref={newNameInput}
fetch("/api/teams/search?" + params.toString()) onChange={e => {
.then(response => response.json()) let params = new URLSearchParams({query: e.target.value, page_len: '5', page_num: '0'});
.then(data => setSearchResults(data)); fetch("/api/teams/search?" + params.toString())
setNewName(e.target.value); .then(response => response.json())
}}/> .then(data => setSearchResults(data));
</div> setNewName(e.target.value);
{searchResults.length > 0 && newName.length > 0 ? }}/>
(<div className="cl_search_list" ref={resultList}> </div>
{searchResults.map(result => {searchResults.length > 0 && newName.length > 0 ?
<div className="cl_search_result" key={result} onClick={e => { (<div className="cl_search_list" ref={resultList}>
props.dispatch({type:'add_team', name: result}); {searchResults.map(result =>
setNewName(""); <div className="cl_search_result" key={result} onClick={e => {
if (newNameInput.current) { props.dispatch({type:'add_team', name: result});
newNameInput.current.focus(); setNewName("");
} if (newNameInput.current) {
}}>{result}</div> newNameInput.current.focus();
)} }
</div>): }}>{result}</div>
<div/> )}
</div>):
null
}</> :
null
} }
<div className="cl_structure_err cl_structure_err_teams">{props.state.teams.length < 2 && props.showError ? "Must have at least 2 teams." : ""}</div>
<div className="cl_structure_err cl_structure_err_teams">{props.showError ? teamsErr : ""}</div>
</div> </div>
); );
} }
@ -446,7 +507,7 @@ function LeagueOptions(props: {state: LeagueOptionsState, dispatch: React.Dispat
); );
} }
function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean, minValue?:number}) { function NumberInput(props: {title: string, value: string, setValue: (newVal: string) => void, showError: boolean, minValue? : number}) {
let minValue = 1; let minValue = 1;
if (props.minValue !== undefined) { if (props.minValue !== undefined) {
minValue = props.minValue minValue = props.minValue
@ -455,7 +516,7 @@ function NumberInput(props: {title: string, value: string, setValue: (newVal: st
<div className="cl_option_box"> <div className="cl_option_box">
<div className="cl_option_label">{props.title}</div> <div className="cl_option_label">{props.title}</div>
<input className="cl_option_input" type="number" min={minValue} value={props.value} onChange={e => props.setValue(e.target.value)}/> <input className="cl_option_input" type="number" min={minValue} value={props.value} onChange={e => props.setValue(e.target.value)}/>
<div className="cl_option_err">{(!isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than "+minValue : ""}</div> <div className="cl_option_err">{(isNaN(Number(props.value)) || Number(props.value) < minValue) && props.showError ? "Must be a number greater than " + minValue : ""}</div>
</div> </div>
); );
} }

View File

@ -1,6 +1,3 @@
import {useRef} from 'react';
function removeIndex(arr: any[], index: number) { function removeIndex(arr: any[], index: number) {
return arr.slice(0, index).concat(arr.slice(index+1)); return arr.slice(0, index).concat(arr.slice(index+1));
} }