Compare commits

..

No commits in common. "2a396457aabc1462dd9cd43b0a308a37cb3ace78" and "1067b12be89b52c2c874fc761447dc231342d162" have entirely different histories.

11 changed files with 90 additions and 155 deletions

8
db.py
View File

@ -73,12 +73,4 @@ class MVPRanking(SQLModel, table=True):
mvps: list[int] = Field(sa_column=Column(ARRAY(Integer)))
class TokenDB(SQLModel, table=True):
token: str = Field(index=True, primary_key=True)
used: bool | None = False
updated_at: datetime | None = Field(
default_factory=utctime, sa_column_kwargs={"onupdate": utctime}
)
SQLModel.metadata.create_all(engine)

View File

@ -1,5 +1,4 @@
from fastapi import APIRouter, Depends, FastAPI, Security
from fastapi.responses import JSONResponse
from fastapi import APIRouter, Depends, FastAPI, Security, status
from fastapi.staticfiles import StaticFiles
from db import Player, Team, Chemistry, MVPRanking, engine
from sqlmodel import (
@ -80,20 +79,18 @@ team_router.add_api_route("/list", endpoint=list_teams, methods=["GET"])
team_router.add_api_route("/add", endpoint=add_team, methods=["POST"])
@api_router.post("/mvps", dependencies=[Depends(get_current_active_user)])
@app.post("/mvps/", status_code=status.HTTP_200_OK)
def submit_mvps(mvps: MVPRanking):
with Session(engine) as session:
session.add(mvps)
session.commit()
return JSONResponse("success!")
@api_router.post("/chemistry", dependencies=[Depends(get_current_active_user)])
@app.post("/chemistry/", status_code=status.HTTP_200_OK)
def submit_chemistry(chemistry: Chemistry):
with Session(engine) as session:
session.add(chemistry)
session.commit()
return JSONResponse("success!")
class SPAStaticFiles(StaticFiles):

View File

@ -19,7 +19,6 @@
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/node": "^22.13.10",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/sortablejs": "^1.15.8",

View File

@ -6,7 +6,7 @@ from pydantic import BaseModel, ValidationError
import jwt
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
from sqlmodel import Session, select
from db import TokenDB, engine, Player
from db import engine, Player
from fastapi.security import (
OAuth2PasswordBearer,
OAuth2PasswordRequestForm,
@ -178,7 +178,6 @@ async def login_for_access_token(
value=access_token,
httponly=True,
samesite="strict",
max_age=config.access_token_expire_minutes * 60,
)
return Token(access_token=access_token)
@ -205,17 +204,6 @@ class FirstPassword(BaseModel):
async def set_first_password(req: FirstPassword):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate token",
)
with Session(engine) as session:
token_in_db = session.exec(
select(TokenDB)
.where(TokenDB.token == req.token)
.where(TokenDB.used == False)
).one_or_none()
if token_in_db:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate token",
@ -235,25 +223,11 @@ async def set_first_password(req: FirstPassword):
user = get_user(username)
if user:
with Session(engine) as session:
user.hashed_password = get_password_hash(req.password)
session.add(user)
token_in_db.used = True
session.add(token_in_db)
session.commit()
return Response(
"Password set successfully", status_code=status.HTTP_200_OK
)
elif session.exec(
select(TokenDB)
.where(TokenDB.token == req.token)
.where(TokenDB.used == True)
).one_or_none():
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token already used",
)
else:
raise credentials_exception
return Response("Password set successfully", status_code=status.HTTP_200_OK)
async def change_password(

View File

@ -17,6 +17,13 @@ import { apiAuth } from "./api";
// };
//};
//
interface Prop {
name: string;
min: string;
max: string;
step: string;
value: string;
}
interface Params {
nodeSize: number;
@ -29,6 +36,12 @@ interface Params {
show: number;
}
interface DeferredProps {
timeout: number;
func: () => void;
}
let timeoutID: NodeJS.Timeout | null = null;
export default function Analysis() {
const [image, setImage] = useState("");
@ -52,10 +65,9 @@ export default function Analysis() {
.then((data) => {
setImage(data.image);
setLoading(false);
})
.catch((e) => {
}).catch((e) => {
console.log("best to just reload... ", e);
});
})
}
useEffect(() => {
@ -69,35 +81,19 @@ export default function Analysis() {
function showLabel() {
switch (params.show) {
case 0:
return "dislike";
case 1:
return "both";
case 2:
return "like";
case 0: return "dislike";
case 1: return "both";
case 2: return "like";
}
}
return (
<div className="stack column dropdown">
<button onClick={() => setShowControlPanel(!showControlPanel)}>
Parameters{" "}
<svg
viewBox="0 0 24 24"
height="1.2em"
style={{
fill: "#ffffff",
display: "inline",
top: "0.2em",
position: "relative",
transform: showControlPanel ? "rotate(180deg)" : "unset",
}}
>
{" "}
<path d="M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z"> </path>
</svg>
Parameters <svg viewBox="0 0 24 24" height="1.2em" style={{ fill: "#ffffff", display: "inline", top: "0.2em", position: "relative", transform: showControlPanel ? "rotate(180deg)" : "unset" }} > <path d="M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z" > </path></svg >
</button>
<div id="control-panel" className={showControlPanel ? "opened" : ""}>
<div className="control">
<datalist id="markers">
<option value="0"></option>
@ -113,9 +109,7 @@ export default function Analysis() {
max="2"
step="1"
width="16px"
onChange={(evt) =>
setParams({ ...params, show: Number(evt.target.value) })
}
onChange={(evt) => setParams({ ...params, show: Number(evt.target.value) })}
/>
<label>😍</label>
</div>
@ -126,9 +120,7 @@ export default function Analysis() {
<input
type="checkbox"
checked={params.weighting}
onChange={(evt) =>
setParams({ ...params, weighting: evt.target.checked })
}
onChange={(evt) => setParams({ ...params, weighting: evt.target.checked })}
/>
<label>weighting</label>
</div>
@ -137,9 +129,7 @@ export default function Analysis() {
<input
type="checkbox"
checked={params.popularity}
onChange={(evt) =>
setParams({ ...params, popularity: evt.target.checked })
}
onChange={(evt) => setParams({ ...params, popularity: evt.target.checked })}
/>
<label>popularity</label>
</div>
@ -153,12 +143,9 @@ export default function Analysis() {
max="3.001"
step="0.05"
value={params.distance}
onChange={(evt) =>
setParams({ ...params, distance: Number(evt.target.value) })
}
onChange={(evt) => setParams({ ...params, distance: Number(evt.target.value) })}
/>
<span>{params.distance}</span>
</div>
<span>{params.distance}</span></div>
<div className="control">
<label>node size</label>
@ -167,9 +154,7 @@ export default function Analysis() {
min="500"
max="3000"
value={params.nodeSize}
onChange={(evt) =>
setParams({ ...params, nodeSize: Number(evt.target.value) })
}
onChange={(evt) => setParams({ ...params, nodeSize: Number(evt.target.value) })}
/>
<span>{params.nodeSize}</span>
</div>
@ -181,9 +166,7 @@ export default function Analysis() {
min="4"
max="24"
value={params.fontSize}
onChange={(evt) =>
setParams({ ...params, fontSize: Number(evt.target.value) })
}
onChange={(evt) => setParams({ ...params, fontSize: Number(evt.target.value) })}
/>
<span>{params.fontSize}</span>
</div>
@ -196,9 +179,7 @@ export default function Analysis() {
max="5"
step="0.1"
value={params.edgeWidth}
onChange={(evt) =>
setParams({ ...params, edgeWidth: Number(evt.target.value) })
}
onChange={(evt) => setParams({ ...params, edgeWidth: Number(evt.target.value) })}
/>
<span>{params.edgeWidth}</span>
</div>
@ -210,19 +191,20 @@ export default function Analysis() {
min="10"
max="50"
value={params.arrowSize}
onChange={(evt) =>
setParams({ ...params, arrowSize: Number(evt.target.value) })
}
onChange={(evt) => setParams({ ...params, arrowSize: Number(evt.target.value) })}
/>
<span>{params.arrowSize}</span>
</div>
</div>
<button onClick={() => loadImage()}>reload </button>
{loading ? (
{
loading ? (
<span className="loader"></span>
) : (
<img src={"data:image/png;base64," + image} width="86%" />
)}
</div>
)
}
</div >
);
}

View File

@ -1,8 +1,10 @@
import { useEffect, useState } from "react";
import { apiAuth } from "./api";
import BarChart from "./BarChart";
import { PlayerRanking } from "./types";
import RaceChart from "./RaceChart";
const MVPChart = () => {
const [data, setData] = useState({} as PlayerRanking[]);
const [loading, setLoading] = useState(true);
@ -11,26 +13,17 @@ const MVPChart = () => {
async function loadData() {
setLoading(true);
await apiAuth("analysis/mvp", null)
.then((json) => json as Promise<PlayerRanking[]>)
.then((json) => {
setData(json.sort((a, b) => a.rank - b.rank));
});
.then(json => json as Promise<PlayerRanking[]>).then(json => { setData(json.sort((a, b) => a.rank - b.rank)) })
setLoading(false);
}
useEffect(() => {
loadData();
}, []);
useEffect(() => { loadData() }, [])
return (
<>
{loading ? (
<span className="loader" />
) : (
<RaceChart std={showStd} players={data} />
)}
</>
);
};
{loading ? <span className="loader" /> : <RaceChart std={showStd} players={data} />
}
</>)
}
export default MVPChart;

View File

@ -1,5 +1,6 @@
import { FC, useEffect, useState } from "react";
import { PlayerRanking } from "./types";
import { useSession } from "./Session";
interface RaceChartProps {
players: PlayerRanking[];

View File

@ -44,12 +44,12 @@ export function Chemistry({ user, players }: PlayerInfoProps) {
const dialog = document.querySelector("dialog[id='ChemistryDialog']");
(dialog as HTMLDialogElement).showModal();
setDialog("sending...");
let left = playersLeft.map(({ id }) => id);
let middle = playersMiddle.map(({ id }) => id);
let right = playersRight.map(({ id }) => id);
const data = { user: user.id, hate: left, undecided: middle, love: right };
const response = await apiAuth("chemistry", data, "POST");
response ? setDialog(response) : setDialog("try sending again");
let left = playersLeft.map(({ display_name }) => display_name);
let middle = playersMiddle.map(({ display_name }) => display_name);
let right = playersRight.map(({ display_name }) => display_name);
const data = { user: user, hate: left, undecided: middle, love: right };
const response = await apiAuth("chemistry", data);
response.ok ? setDialog("success!") : setDialog("try sending again");
}
return (
@ -124,10 +124,10 @@ export function MVP({ user, players }: PlayerInfoProps) {
const dialog = document.querySelector("dialog[id='MVPDialog']");
(dialog as HTMLDialogElement).showModal();
setDialog("sending...");
let mvps = rankedPlayers.map(({ id }) => id);
const data = { user: user.id, mvps: mvps };
const response = await apiAuth("mvps", data, "POST");
response ? setDialog(response) : setDialog("try sending again");
let mvps = rankedPlayers.map(({ display_name }) => display_name);
const data = { user: user, mvps: mvps };
const response = await apiAuth("mvps", data);
response.ok ? setDialog("success!") : setDialog("try sending again");
}
return (

View File

@ -64,6 +64,7 @@ export function SessionProvider(props: SessionProviderProps) {
setErr(e);
}
}
console.log("sanity", user);
let content: ReactNode;
if (loading || (!err && !user))

View File

@ -1,7 +1,7 @@
import { jwtDecode, JwtPayload } from "jwt-decode";
import { InvalidTokenError, jwtDecode, JwtPayload } from "jwt-decode";
import { useEffect, useState } from "react";
import { baseUrl } from "./api";
import { useNavigate } from "react-router";
import { redirect, useNavigate } from "react-router";
interface SetPassToken extends JwtPayload {
name: string;
@ -63,9 +63,9 @@ export const SetPassword = () => {
if (!resp.ok) {
if (resp.status === 401) {
const { detail } = await resp.json();
if (detail) setError(detail);
else setError("unauthorized");
resp.statusText
? setError(resp.statusText)
: setError("unauthorized");
throw new Error("Unauthorized");
}
}

View File

@ -3,13 +3,10 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
@ -17,6 +14,7 @@
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
@ -24,7 +22,5 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": [
"src"
]
"include": ["src"]
}