feat: Added GET /api/v1/techniques/random endpoint returning {slug}, fe…
- "backend/routers/techniques.py" - "frontend/src/api/public-client.ts" - "frontend/src/pages/Home.tsx" - "frontend/src/App.css" GSD-Task: S01/T02
This commit is contained in:
parent
9e4f10b0af
commit
df559bbca0
4 changed files with 72 additions and 0 deletions
|
|
@ -182,6 +182,19 @@ async def list_techniques(
|
|||
)
|
||||
|
||||
|
||||
@router.get("/random")
|
||||
async def random_technique(
|
||||
db: AsyncSession = Depends(get_session),
|
||||
) -> dict:
|
||||
"""Return the slug of a single random technique page."""
|
||||
stmt = select(TechniquePage.slug).order_by(func.random()).limit(1)
|
||||
result = await db.execute(stmt)
|
||||
slug = result.scalar_one_or_none()
|
||||
if slug is None:
|
||||
raise HTTPException(status_code=404, detail="No techniques available")
|
||||
return {"slug": slug}
|
||||
|
||||
|
||||
@router.get("/{slug}", response_model=TechniquePageDetail)
|
||||
async def get_technique(
|
||||
slug: str,
|
||||
|
|
|
|||
|
|
@ -2729,6 +2729,33 @@ a.app-footer__repo:hover {
|
|||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.btn--random {
|
||||
background: var(--color-bg-input);
|
||||
color: var(--color-text-primary);
|
||||
border-color: var(--color-border);
|
||||
font-size: 0.95rem;
|
||||
padding: 0.6rem 1.4rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
transition: background 0.15s, border-color 0.15s, transform 0.15s;
|
||||
}
|
||||
|
||||
.btn--random:hover:not(:disabled) {
|
||||
background: var(--color-border);
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.btn--random:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.home-random {
|
||||
text-align: center;
|
||||
margin: 1.5rem 0 0.5rem;
|
||||
}
|
||||
|
||||
/* ── Admin Reports ──────────────────────────────────────────────────────── */
|
||||
|
||||
.admin-reports {
|
||||
|
|
|
|||
|
|
@ -258,6 +258,10 @@ export async function fetchTechnique(
|
|||
return request<TechniquePageDetail>(`${BASE}/techniques/${slug}`);
|
||||
}
|
||||
|
||||
export async function fetchRandomTechnique(): Promise<{ slug: string }> {
|
||||
return request<{ slug: string }>(`${BASE}/techniques/random`);
|
||||
}
|
||||
|
||||
export async function fetchTechniqueVersions(
|
||||
slug: string,
|
||||
): Promise<TechniquePageVersionListResponse> {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { Link, useNavigate } from "react-router-dom";
|
|||
import {
|
||||
fetchTechniques,
|
||||
fetchTopics,
|
||||
fetchRandomTechnique,
|
||||
type TechniqueListItem,
|
||||
} from "../api/public-client";
|
||||
|
||||
|
|
@ -20,8 +21,24 @@ export default function Home() {
|
|||
const [recent, setRecent] = useState<TechniqueListItem[]>([]);
|
||||
const [recentLoading, setRecentLoading] = useState(true);
|
||||
const [popularTopics, setPopularTopics] = useState<{name: string; count: number}[]>([]);
|
||||
const [randomLoading, setRandomLoading] = useState(false);
|
||||
const [randomError, setRandomError] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleRandomTechnique = async () => {
|
||||
setRandomLoading(true);
|
||||
setRandomError(false);
|
||||
try {
|
||||
const { slug } = await fetchRandomTechnique();
|
||||
navigate(`/techniques/${slug}`);
|
||||
} catch {
|
||||
setRandomError(true);
|
||||
setTimeout(() => setRandomError(false), 2000);
|
||||
} finally {
|
||||
setRandomLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Load featured technique (random)
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
|
@ -156,6 +173,17 @@ export default function Home() {
|
|||
</Link>
|
||||
</section>
|
||||
|
||||
{/* Random technique discovery */}
|
||||
<div className="home-random">
|
||||
<button
|
||||
className="btn btn--random"
|
||||
onClick={handleRandomTechnique}
|
||||
disabled={randomLoading}
|
||||
>
|
||||
{randomLoading ? "Loading…" : randomError ? "Try again" : "🎲 Random Technique"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Featured Technique Spotlight */}
|
||||
{featured && (
|
||||
<section className="home-featured">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue