Skip to content

Commit f0dc6e2

Browse files
authored
Merge pull request #18 from mark-mdev/cap-deploy
feat(auth, story): enhance type safety and improve hooks
2 parents eb6b789 + fbc93d4 commit f0dc6e2

File tree

5 files changed

+121
-11
lines changed

5 files changed

+121
-11
lines changed

apps/backend/API.md

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ Provides endpoints for authentication, story generation, vocabulary management,
3737

3838
### Security
3939

40-
- Uses HTTP-only, Secure JWT cookies (`accessToken`, `refreshToken`).
41-
- CORS restricted to frontend origin.
40+
- Uses HTTP-only JWT cookies (`accessToken`, `refreshToken`).
41+
- `accessToken` TTL: 15 minutes
42+
- `refreshToken` TTL: 7 days
43+
- Cookies are `httpOnly`, `sameSite: lax`; `secure` is enabled in production
44+
- CORS: open by default in development; in production, configure allowed origins at the reverse proxy or app level.
4245

4346
### Rate Limiting
4447

45-
- Default: 1000 requests per 15 minutes per authenticated user.
48+
- Global: 1000 requests per 15 minutes per client.
49+
- Story generation daily limit: 5 stories per user per calendar day (America/Los_Angeles); exceeding returns `429`.
4650

4751
---
4852

@@ -58,6 +62,7 @@ Provides endpoints for authentication, story generation, vocabulary management,
5862
| 403 | Forbidden |
5963
| 404 | Not Found |
6064
| 429 | Too Many Requests |
65+
| 503 | Service Unavailable |
6166
| 502 | Database / Storage error |
6267
| 500 | Internal Server Error |
6368

@@ -137,6 +142,16 @@ Provides endpoints for authentication, story generation, vocabulary management,
137142
}
138143
```
139144

145+
### 503 Service Unavailable (readiness)
146+
147+
```json
148+
{
149+
"status": "unhealthy",
150+
"dbOk": false,
151+
"redisOk": true
152+
}
153+
```
154+
140155
### 502 Database Error
141156

142157
```json
@@ -266,7 +281,7 @@ Create a new user and issue auth cookies.
266281

267282
- Responses
268283

269-
- `201 Created`
284+
- `200 OK`
270285

271286
```json
272287
{
@@ -409,6 +424,9 @@ Start asynchronous story generation.
409424

410425
- Then poll: `GET /jobs/status/{jobId}` or fetch `GET /story` after completion
411426

427+
- Errors
428+
- `429 Too Many Requests` when daily story limit is reached
429+
412430
Example completed job:
413431

414432
```json
@@ -660,7 +678,7 @@ Add multiple vocabulary entries in a single request.
660678

661679
- Responses
662680

663-
- `200 OK`
681+
- `201 Created`
664682

665683
```json
666684
{
@@ -837,3 +855,80 @@ Get status of a background job.
837855
- `404 Not Found` if job is missing
838856

839857
---
858+
859+
### Onboarding
860+
861+
#### POST `/onboarding/complete`
862+
863+
Mark onboarding as completed for the current user.
864+
865+
- Authentication: Required
866+
- Request Body: none
867+
- Responses
868+
869+
- `200 OK`
870+
871+
```json
872+
{ "success": true }
873+
```
874+
875+
---
876+
877+
#### GET `/onboarding/check`
878+
879+
Get onboarding status for the current user.
880+
881+
- Authentication: Required
882+
- Responses
883+
884+
- `200 OK`
885+
886+
```json
887+
{ "success": true, "data": { "status": "completed" } }
888+
```
889+
890+
or
891+
892+
```json
893+
{ "success": true, "data": { "status": "not_started" } }
894+
```
895+
896+
---
897+
898+
### Health
899+
900+
#### GET `/healthz`
901+
902+
Liveness probe.
903+
904+
- Authentication: None
905+
- Responses
906+
907+
- `200 OK`
908+
909+
```json
910+
{ "status": "ok" }
911+
```
912+
913+
---
914+
915+
#### GET `/readyz`
916+
917+
Readiness probe (checks DB and Redis).
918+
919+
- Authentication: None
920+
- Responses
921+
922+
- `200 OK` when healthy
923+
924+
```json
925+
{ "status": "ok", "dbOk": true, "redisOk": true }
926+
```
927+
928+
- `503 Service Unavailable` when not ready
929+
930+
```json
931+
{ "status": "unhealthy", "dbOk": false, "redisOk": false }
932+
```
933+
934+
---

apps/frontend/src/features/auth/hooks/useAuthRedirect.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { ApiError } from "@/types/ApiError";
12
import { useRouter } from "next/navigation";
23
import { useEffect } from "react";
34

4-
export default function useAuthRedirect(error?: any) {
5+
export default function useAuthRedirect(error?: ApiError) {
56
const router = useRouter();
67
useEffect(() => {
78
if (error?.statusCode === 401) {

apps/frontend/src/features/story/hooks/useStories.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { StoryApi } from "@/features/story/api";
22
import { ClientApi } from "@/lib/ClientApi";
3-
import useSWR from "swr";
3+
import { ApiError } from "@/types/ApiError";
4+
import useSWR, { KeyedMutator } from "swr";
5+
import { Story } from "../types";
46

5-
export function useStories() {
7+
export function useStories(): {
8+
stories: Story[] | undefined;
9+
error: ApiError;
10+
isLoading: boolean;
11+
mutateStories: KeyedMutator<Story[]>;
12+
} {
613
const clientApi = new ClientApi();
714
const storyApi = new StoryApi(clientApi);
815
const { data, error, isLoading, mutate } = useSWR("/api/story", () => storyApi.getAllStories());

apps/frontend/src/features/story/hooks/useWordStatus.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback } from "react";
1+
import { useCallback, useMemo } from "react";
22
import { Story } from "@/features/story/types";
33
import { UnknownWordApi } from "@/features/unknownWord/api";
44
import { ClientApi } from "@/lib/ClientApi";
@@ -7,8 +7,8 @@ import { toast } from "react-toastify";
77
import { KeyedMutator } from "swr";
88

99
export function useWordStatus(chosenStory: Story | null, mutate: KeyedMutator<Story[]>) {
10-
const clientApi = new ClientApi();
11-
const unknownWordApi = new UnknownWordApi(clientApi);
10+
const clientApi = useMemo(() => new ClientApi(), []);
11+
const unknownWordApi = useMemo(() => new UnknownWordApi(clientApi), [clientApi]);
1212

1313
const updateCurrentDataWithNewWordStatus = useCallback(
1414
(currentData: Story[] | undefined, wordId: number, newStatus: "learned" | "learning") => {

apps/landing/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM node:alpine
2+
WORKDIR "/app"
3+
COPY ./package*.json .
4+
RUN npm install
5+
COPY . .
6+
RUN npm run build
7+
CMD ["npm", "run", "start"]

0 commit comments

Comments
 (0)