HotTRDealsBackend/docs/frontend-api.md
2026-01-29 00:45:52 +00:00

319 lines
7.8 KiB
Markdown

# Frontend API Guide (HotTRDeals)
This file is a frontend-focused summary of current backend routes, auth, and response models.
Server entry: `server.js`.
## Base URL and CORS
- Local API base: `http://localhost:3000`
- All routes below are relative to `/api`.
- CORS in dev allows `http://localhost:5173` and `credentials: true`.
## Auth summary
- Access token is returned in response body: `{ token, user }`.
- Send access token via header: `Authorization: Bearer <token>`.
- Refresh token is stored in httpOnly cookie named `rt`.
- For `/auth/refresh` and `/auth/logout`, the frontend must send cookies (`withCredentials: true`).
- Cookie options:
- Dev: `secure=false`, `sameSite=lax`
- Prod: `secure=true`, `sameSite=none`
## Roles
- Roles: `USER`, `MOD`, `ADMIN`.
- `requireRole("MOD")` means only MOD and ADMIN can access.
## Common response shapes
- Errors are inconsistent:
- Some endpoints return `{ error: "..." }`
- Some endpoints return `{ message: "..." }`
- Expect both in frontend error handling.
## Common models (from adapters/contracts)
### PublicUserSummary
```
{
id: number,
username: string,
avatarUrl: string | null
}
```
### PublicUserDetails
```
{
id: number,
username: string,
avatarUrl: string | null,
email: string,
createdAt: string (ISO)
}
```
### AuthUser
```
{
id: number,
username: string,
email: string,
role: "USER" | "MOD" | "ADMIN",
avatarUrl: string | null
}
```
### DealCard
```
{
id: number,
title: string,
description: string,
price: number | null,
originalPrice?: number | null,
shippingPrice?: number | null,
score: number,
commentsCount: number,
url: string | null,
status: "PENDING" | "ACTIVE" | "EXPIRED" | "REJECTED",
saleType: "ONLINE" | "OFFLINE" | "CODE",
affiliateType: "AFFILIATE" | "NON_AFFILIATE" | "USER_AFFILIATE",
myVote: -1 | 0 | 1,
createdAt: string (ISO),
updatedAt: string (ISO),
user: PublicUserSummary,
seller: { name: string, url: string | null },
imageUrl: string
}
```
### DealDetail
```
{
id: number,
title: string,
description: string,
url: string | null,
price: number | null,
originalPrice?: number | null,
shippingPrice?: number | null,
score: number,
commentsCount: number,
status: "PENDING" | "ACTIVE" | "EXPIRED" | "REJECTED",
saleType: "ONLINE" | "OFFLINE" | "CODE",
affiliateType: "AFFILIATE" | "NON_AFFILIATE" | "USER_AFFILIATE",
createdAt: string (ISO),
updatedAt: string (ISO),
user: PublicUserSummary,
seller: { id: number, name: string, url: string | null },
images: [{ id: number, imageUrl: string, order: number }],
comments: [{ id: number, text: string, createdAt: string, parentId?: number | null, user: PublicUserSummary }],
breadcrumb: [{ id: number, name: string, slug: string }],
notice: {
id: number,
dealId: number,
title: string,
body: string | null,
severity: "INFO" | "WARNING" | "DANGER" | "SUCCESS",
isActive: boolean,
createdBy: number,
createdAt: string,
updatedAt: string
} | null,
similarDeals: [{ id: number, title: string, price: number | null, score: number, imageUrl: string, sellerName: string, createdAt: string | null }]
}
```
### DealListResponse
```
{
page: number,
total: number,
totalPages: number,
results: DealCard[]
}
```
### Comment (DealComment)
```
{
id: number,
text: string,
createdAt: string,
parentId?: number | null,
user: PublicUserSummary
}
```
### UserProfile
```
{
user: PublicUserDetails,
stats: { totalLikes: number, totalShares: number, totalComments: number, totalDeals: number },
deals: DealCard[],
comments: [{ ...Comment, deal: { id: number, title: string } }]
}
```
### VoteResponse
```
{
dealId: number,
voteType: -1 | 0 | 1,
delta: number,
score: number | null
}
```
### VoteListResponse
```
{
votes: [{ id, dealId, userId, voteType, createdAt, lastVotedAt }]
}
```
## Endpoints
### Auth (`/api/auth`)
- `POST /register`
- Body: `{ username, email, password }`
- Response: `{ token, user: AuthUser }` and sets `rt` cookie if refresh token exists.
- `POST /login`
- Body: `{ email, password }`
- Response: `{ token, user: AuthUser }` and sets `rt` cookie.
- `POST /refresh`
- Cookie required: `rt`
- Response: `{ token, user: AuthUser }` and rotates `rt` cookie.
- `POST /logout`
- Cookie optional: `rt`
- Response: `204 No Content`, clears `rt` cookie.
- `GET /me`
- Auth: required
- Response: `AuthUser`
- Note: current implementation reads `req.user.userId` in adapter, while auth middleware sets `req.auth`. If `req.user` is undefined, this endpoint can fail.
### Account (`/api/account`)
- `POST /avatar`
- Auth: required
- Multipart: `file` (single)
- Validation: JPEG only, max 2MB
- Response: `{ message, user: PublicUserSummary }`
- `GET /me`
- Auth: required
- Response: `PublicUserDetails`
### Deals (`/api/deals`)
List query params (used in list endpoints):
- `q` (string, default "")
- `page` (int, default 1)
- `limit` (int, default 10, max 100)
Endpoints:
- `GET /users/:userName/deals`
- Auth: optional
- Response: `DealListResponse`
- `GET /me/deals`
- Auth: required
- Response: `DealListResponse`
- `GET /new`
- `GET /hot`
- `GET /trending`
- `GET /` (same as `/new`)
- Auth: optional
- Response: `DealListResponse`
- `GET /search`
- Auth: optional
- If `q` is empty: `{ results: [], total: 0, totalPages: 0, page }`
- Else: `DealListResponse`
- `GET /top`
- Auth: optional
- Query: `range=day|week|month` (default `day`), `limit` (default 6, max 20)
- Response: `DealCard[]` (array only)
- `GET /:id`
- Response: `DealDetail`
- `POST /`
- Auth: required
- Multipart: `images` (array, max 5)
- Limits: 10MB per file (upload middleware)
- Body fields: `{ title, description?, url?, price?, sellerName? }`
- Response: `DealDetail`
### Category (`/api/category`)
- `GET /:slug`
- Response: `{ id, name, slug, description, breadcrumb }`
- `GET /:slug/deals`
- Auth: optional
- Query: `page`, `limit`, plus filters from query
- Response: `DealCard[]` (array only)
### Seller (`/api/seller`)
- `POST /from-link`
- Auth: required
- Body: `{ url }`
- Response: `{ found: boolean, seller: { id, name, url } | null }`
- `GET /:sellerName`
- Response: `{ id, name, url, logoUrl }`
- `GET /:sellerName/deals`
- Auth: optional
- Query: `page`, `limit`, `q`
- Response: `DealCard[]` (array only)
### Comments (`/api/comments`)
- `GET /:dealId`
- Response: `Comment[]`
- `POST /`
- Auth: required
- Body: `{ dealId, text, parentId? }`
- Response: `Comment`
- `DELETE /:id`
- Auth: required
- Response: `{ message: "Yorum silindi." }`
### Votes (`/api/vote` and `/api/deal-votes`)
- `POST /`
- Auth: required
- Body: `{ dealId, voteType }` where voteType is -1, 0, or 1
- Response: `VoteResponse`
- `GET /:dealId`
- Response: `VoteListResponse`
### Users (`/api/user` and `/api/users`)
- `GET /:userName`
- Response: `UserProfile`
### Mod (`/api/mod`) (MOD or ADMIN)
- `GET /deals/pending`
- Auth + Role: MOD
- Query: `page`, `limit`, `q` plus filters
- Response: `DealCard[]` (array only)
- `POST /deals/:id/approve`
- `POST /deals/:id/reject`
- `POST /deals/:id/expire`
- `POST /deals/:id/unexpire`
- Auth + Role: MOD
- Response: `{ id, status }`
## Notes for frontend
- Some list endpoints return full pagination object, some return only `results` array. Treat them separately:
- Full: `/api/deals` list endpoints, `/api/deals/search`, `/api/deals/users/:userName/deals`, `/api/deals/me/deals`
- Array only: `/api/deals/top`, `/api/category/:slug/deals`, `/api/seller/:sellerName/deals`, `/api/mod/deals/pending`
- Optional-auth endpoints return `401` if a token is provided but invalid.
- MyVote is only filled when Authorization header is present.
- There are duplicate route prefixes for users and votes. Frontend can use either, but pick one for consistency.