second round of review

This commit is contained in:
counterweight 2025-12-18 22:31:19 +01:00
parent da5a0d03eb
commit ca55932a41
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
10 changed files with 124 additions and 229 deletions

View file

@ -18,14 +18,13 @@ ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days
COOKIE_NAME = "auth_token"
class UserCreate(BaseModel):
class UserCredentials(BaseModel):
email: EmailStr
password: str
class UserLogin(BaseModel):
email: EmailStr
password: str
UserCreate = UserCredentials
UserLogin = UserCredentials
class UserResponse(BaseModel):

7
backend/tests/helpers.py Normal file
View file

@ -0,0 +1,7 @@
import uuid
def unique_email(prefix: str = "test") -> str:
"""Generate a unique email for tests sharing the same database."""
return f"{prefix}-{uuid.uuid4().hex[:8]}@example.com"

View file

@ -1,12 +1,7 @@
import pytest
import uuid
from auth import COOKIE_NAME
def unique_email(prefix: str = "test") -> str:
"""Generate a unique email for tests sharing the same database."""
return f"{prefix}-{uuid.uuid4().hex[:8]}@example.com"
from tests.helpers import unique_email
# Registration tests

View file

@ -1,12 +1,7 @@
import pytest
import uuid
from auth import COOKIE_NAME
def unique_email(prefix: str = "counter") -> str:
"""Generate a unique email for tests sharing the same database."""
return f"{prefix}-{uuid.uuid4().hex[:8]}@example.com"
from tests.helpers import unique_email
# Protected endpoint tests - without auth

View file

@ -2,7 +2,7 @@
import { createContext, useContext, useState, useEffect, ReactNode } from "react";
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
import { API_URL } from "./config";
interface User {
id: number;

2
frontend/app/config.ts Normal file
View file

@ -0,0 +1,2 @@
export const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";

View file

@ -3,6 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "../auth-context";
import { authFormStyles as styles } from "../styles/auth-form";
export default function LoginPage() {
const [email, setEmail] = useState("");
@ -88,108 +89,3 @@ export default function LoginPage() {
</main>
);
}
const styles: Record<string, React.CSSProperties> = {
main: {
minHeight: "100vh",
background: "linear-gradient(135deg, #0f0f23 0%, #1a1a3e 50%, #2d1b4e 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "1rem",
},
container: {
width: "100%",
maxWidth: "420px",
},
card: {
background: "rgba(255, 255, 255, 0.03)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "24px",
padding: "3rem 2.5rem",
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
},
header: {
textAlign: "center" as const,
marginBottom: "2.5rem",
},
title: {
fontFamily: "'Instrument Serif', Georgia, serif",
fontSize: "2.5rem",
fontWeight: 400,
color: "#fff",
margin: 0,
letterSpacing: "-0.02em",
},
subtitle: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.5)",
marginTop: "0.5rem",
fontSize: "0.95rem",
},
form: {
display: "flex",
flexDirection: "column" as const,
gap: "1.5rem",
},
field: {
display: "flex",
flexDirection: "column" as const,
gap: "0.5rem",
},
label: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.7)",
fontSize: "0.875rem",
fontWeight: 500,
},
input: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.875rem 1rem",
fontSize: "1rem",
background: "rgba(255, 255, 255, 0.05)",
border: "1px solid rgba(255, 255, 255, 0.1)",
borderRadius: "12px",
color: "#fff",
outline: "none",
transition: "border-color 0.2s, box-shadow 0.2s",
},
button: {
fontFamily: "'DM Sans', system-ui, sans-serif",
marginTop: "0.5rem",
padding: "1rem",
fontSize: "1rem",
fontWeight: 600,
background: "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
color: "#fff",
border: "none",
borderRadius: "12px",
cursor: "pointer",
transition: "transform 0.2s, box-shadow 0.2s",
boxShadow: "0 4px 14px rgba(99, 102, 241, 0.4)",
},
error: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.875rem 1rem",
background: "rgba(239, 68, 68, 0.1)",
border: "1px solid rgba(239, 68, 68, 0.3)",
borderRadius: "12px",
color: "#fca5a5",
fontSize: "0.875rem",
textAlign: "center" as const,
},
footer: {
fontFamily: "'DM Sans', system-ui, sans-serif",
textAlign: "center" as const,
marginTop: "2rem",
color: "rgba(255, 255, 255, 0.5)",
fontSize: "0.875rem",
},
link: {
color: "#a78bfa",
textDecoration: "none",
fontWeight: 500,
},
};

View file

@ -3,8 +3,7 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "./auth-context";
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
import { API_URL } from "./config";
export default function Home() {
const [count, setCount] = useState<number | null>(null);

View file

@ -3,6 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "../auth-context";
import { authFormStyles as styles } from "../styles/auth-form";
export default function SignupPage() {
const [email, setEmail] = useState("");
@ -113,108 +114,3 @@ export default function SignupPage() {
</main>
);
}
const styles: Record<string, React.CSSProperties> = {
main: {
minHeight: "100vh",
background: "linear-gradient(135deg, #0f0f23 0%, #1a1a3e 50%, #2d1b4e 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "1rem",
},
container: {
width: "100%",
maxWidth: "420px",
},
card: {
background: "rgba(255, 255, 255, 0.03)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "24px",
padding: "3rem 2.5rem",
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
},
header: {
textAlign: "center" as const,
marginBottom: "2.5rem",
},
title: {
fontFamily: "'Instrument Serif', Georgia, serif",
fontSize: "2.5rem",
fontWeight: 400,
color: "#fff",
margin: 0,
letterSpacing: "-0.02em",
},
subtitle: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.5)",
marginTop: "0.5rem",
fontSize: "0.95rem",
},
form: {
display: "flex",
flexDirection: "column" as const,
gap: "1.5rem",
},
field: {
display: "flex",
flexDirection: "column" as const,
gap: "0.5rem",
},
label: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.7)",
fontSize: "0.875rem",
fontWeight: 500,
},
input: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.875rem 1rem",
fontSize: "1rem",
background: "rgba(255, 255, 255, 0.05)",
border: "1px solid rgba(255, 255, 255, 0.1)",
borderRadius: "12px",
color: "#fff",
outline: "none",
transition: "border-color 0.2s, box-shadow 0.2s",
},
button: {
fontFamily: "'DM Sans', system-ui, sans-serif",
marginTop: "0.5rem",
padding: "1rem",
fontSize: "1rem",
fontWeight: 600,
background: "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
color: "#fff",
border: "none",
borderRadius: "12px",
cursor: "pointer",
transition: "transform 0.2s, box-shadow 0.2s",
boxShadow: "0 4px 14px rgba(99, 102, 241, 0.4)",
},
error: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.875rem 1rem",
background: "rgba(239, 68, 68, 0.1)",
border: "1px solid rgba(239, 68, 68, 0.3)",
borderRadius: "12px",
color: "#fca5a5",
fontSize: "0.875rem",
textAlign: "center" as const,
},
footer: {
fontFamily: "'DM Sans', system-ui, sans-serif",
textAlign: "center" as const,
marginTop: "2rem",
color: "rgba(255, 255, 255, 0.5)",
fontSize: "0.875rem",
},
link: {
color: "#a78bfa",
textDecoration: "none",
fontWeight: 500,
},
};

View file

@ -0,0 +1,106 @@
import { CSSProperties } from "react";
export const authFormStyles: Record<string, CSSProperties> = {
main: {
minHeight: "100vh",
background: "linear-gradient(135deg, #0f0f23 0%, #1a1a3e 50%, #2d1b4e 100%)",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "1rem",
},
container: {
width: "100%",
maxWidth: "420px",
},
card: {
background: "rgba(255, 255, 255, 0.03)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255, 255, 255, 0.08)",
borderRadius: "24px",
padding: "3rem 2.5rem",
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)",
},
header: {
textAlign: "center" as const,
marginBottom: "2.5rem",
},
title: {
fontFamily: "'Instrument Serif', Georgia, serif",
fontSize: "2.5rem",
fontWeight: 400,
color: "#fff",
margin: 0,
letterSpacing: "-0.02em",
},
subtitle: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.5)",
marginTop: "0.5rem",
fontSize: "0.95rem",
},
form: {
display: "flex",
flexDirection: "column" as const,
gap: "1.5rem",
},
field: {
display: "flex",
flexDirection: "column" as const,
gap: "0.5rem",
},
label: {
fontFamily: "'DM Sans', system-ui, sans-serif",
color: "rgba(255, 255, 255, 0.7)",
fontSize: "0.875rem",
fontWeight: 500,
},
input: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.875rem 1rem",
fontSize: "1rem",
background: "rgba(255, 255, 255, 0.05)",
border: "1px solid rgba(255, 255, 255, 0.1)",
borderRadius: "12px",
color: "#fff",
outline: "none",
transition: "border-color 0.2s, box-shadow 0.2s",
},
button: {
fontFamily: "'DM Sans', system-ui, sans-serif",
marginTop: "0.5rem",
padding: "1rem",
fontSize: "1rem",
fontWeight: 600,
background: "linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
color: "#fff",
border: "none",
borderRadius: "12px",
cursor: "pointer",
transition: "transform 0.2s, box-shadow 0.2s",
boxShadow: "0 4px 14px rgba(99, 102, 241, 0.4)",
},
error: {
fontFamily: "'DM Sans', system-ui, sans-serif",
padding: "0.875rem 1rem",
background: "rgba(239, 68, 68, 0.1)",
border: "1px solid rgba(239, 68, 68, 0.3)",
borderRadius: "12px",
color: "#fca5a5",
fontSize: "0.875rem",
textAlign: "center" as const,
},
footer: {
fontFamily: "'DM Sans', system-ui, sans-serif",
textAlign: "center" as const,
marginTop: "2rem",
color: "rgba(255, 255, 255, 0.5)",
fontSize: "0.875rem",
},
link: {
color: "#a78bfa",
textDecoration: "none",
fontWeight: 500,
},
};