second round of review
This commit is contained in:
parent
da5a0d03eb
commit
ca55932a41
10 changed files with 124 additions and 229 deletions
|
|
@ -18,14 +18,13 @@ ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days
|
||||||
COOKIE_NAME = "auth_token"
|
COOKIE_NAME = "auth_token"
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(BaseModel):
|
class UserCredentials(BaseModel):
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
class UserLogin(BaseModel):
|
UserCreate = UserCredentials
|
||||||
email: EmailStr
|
UserLogin = UserCredentials
|
||||||
password: str
|
|
||||||
|
|
||||||
|
|
||||||
class UserResponse(BaseModel):
|
class UserResponse(BaseModel):
|
||||||
|
|
|
||||||
7
backend/tests/helpers.py
Normal file
7
backend/tests/helpers.py
Normal 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"
|
||||||
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
import uuid
|
|
||||||
|
|
||||||
from auth import COOKIE_NAME
|
from auth import COOKIE_NAME
|
||||||
|
from tests.helpers import unique_email
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
# Registration tests
|
# Registration tests
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
import pytest
|
import pytest
|
||||||
import uuid
|
|
||||||
|
|
||||||
from auth import COOKIE_NAME
|
from auth import COOKIE_NAME
|
||||||
|
from tests.helpers import unique_email
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
# Protected endpoint tests - without auth
|
# Protected endpoint tests - without auth
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { createContext, useContext, useState, useEffect, ReactNode } from "react";
|
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 {
|
interface User {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
||||||
2
frontend/app/config.ts
Normal file
2
frontend/app/config.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
||||||
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "../auth-context";
|
import { useAuth } from "../auth-context";
|
||||||
|
import { authFormStyles as styles } from "../styles/auth-form";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
|
|
@ -88,108 +89,3 @@ export default function LoginPage() {
|
||||||
</main>
|
</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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "./auth-context";
|
import { useAuth } from "./auth-context";
|
||||||
|
import { API_URL } from "./config";
|
||||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [count, setCount] = useState<number | null>(null);
|
const [count, setCount] = useState<number | null>(null);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useAuth } from "../auth-context";
|
import { useAuth } from "../auth-context";
|
||||||
|
import { authFormStyles as styles } from "../styles/auth-form";
|
||||||
|
|
||||||
export default function SignupPage() {
|
export default function SignupPage() {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
|
|
@ -113,108 +114,3 @@ export default function SignupPage() {
|
||||||
</main>
|
</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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
||||||
106
frontend/app/styles/auth-form.ts
Normal file
106
frontend/app/styles/auth-form.ts
Normal 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue