Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,211 changes: 1,209 additions & 2,002 deletions package-lock.json

Large diffs are not rendered by default.

56 changes: 33 additions & 23 deletions src/app/api/auth/login/route.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,65 @@
import { NextRequest, NextResponse } from 'next/server';
import type { AuthResponse } from '@/types/api';
import { withRateLimit } from '@/lib/ratelimit';

export async function POST(
request: NextRequest,
): Promise<NextResponse<AuthResponse | { message: string }>> {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'AUTH');
if (rateLimitResponse) {
return rateLimitResponse as NextResponse;

Check failure on line 10 in src/app/api/auth/login/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'NextResponse<unknown>' is not assignable to type 'NextResponse<AuthResponse | { message: string; }>'.
}

try {
const body = await request.json();
const { email, password } = body;

// Mock validation
if (!email || !password) {
return NextResponse.json({ message: 'Email and password are required' }, { status: 400 });
return addHeaders(NextResponse.json({ message: 'Email and password are required' }, { status: 400 }));

Check failure on line 19 in src/app/api/auth/login/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
}

// Mock authentication - check for demo credentials
if (email === 'demo@teachlink.com' && password === 'password123') {
// Simulate successful login
return NextResponse.json(
{
message: 'Login successful',
user: {
id: '1',
name: 'Demo User',
email: email,
return addHeaders(

Check failure on line 25 in src/app/api/auth/login/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
NextResponse.json(
{
message: 'Login successful',
user: {
id: '1',
name: 'Demo User',
email: email,
},
token: 'mock-jwt-token-' + Date.now(),
},
token: 'mock-jwt-token-' + Date.now(),
},
{ status: 200 },
{ status: 200 },
),
);
}

// Mock: Accept any valid email format with password length >= 6
if (password.length >= 6) {
return NextResponse.json(
{
message: 'Login successful',
user: {
id: Math.random().toString(36).substr(2, 9),
name: email.split('@')[0],
email: email,
return addHeaders(

Check failure on line 43 in src/app/api/auth/login/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
NextResponse.json(
{
message: 'Login successful',
user: {
id: Math.random().toString(36).substr(2, 9),
name: email.split('@')[0],
email: email,
},
token: 'mock-jwt-token-' + Date.now(),
},
token: 'mock-jwt-token-' + Date.now(),
},
{ status: 200 },
{ status: 200 },
),
);
}

// Invalid credentials
return NextResponse.json({ message: 'Invalid email or password' }, { status: 401 });
return addHeaders(NextResponse.json({ message: 'Invalid email or password' }, { status: 401 }));

Check failure on line 60 in src/app/api/auth/login/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
} catch (error) {
console.error('Login error:', error);
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
return addHeaders(NextResponse.json({ message: 'Internal server error' }, { status: 500 }));

Check failure on line 63 in src/app/api/auth/login/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
}
}
44 changes: 24 additions & 20 deletions src/app/api/auth/signup/route.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';
import type { AuthResponse } from '@/types/api';
import { withRateLimit } from '@/lib/ratelimit';

export async function POST(
request: NextRequest,
): Promise<NextResponse<AuthResponse | { message: string }>> {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'AUTH');
if (rateLimitResponse) {
return rateLimitResponse as NextResponse;

Check failure on line 10 in src/app/api/auth/signup/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'NextResponse<unknown>' is not assignable to type 'NextResponse<AuthResponse | { message: string; }>'.
}

try {
const body = await request.json();
const { name, email, password, confirmPassword } = body;

// Mock validation
if (!name || !email || !password || !confirmPassword) {
return NextResponse.json({ message: 'All fields are required' }, { status: 400 });
return addHeaders(NextResponse.json({ message: 'All fields are required' }, { status: 400 }));

Check failure on line 18 in src/app/api/auth/signup/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
}

if (password !== confirmPassword) {
return NextResponse.json({ message: "Passwords don't match" }, { status: 400 });
return addHeaders(NextResponse.json({ message: "Passwords don't match" }, { status: 400 }));

Check failure on line 22 in src/app/api/auth/signup/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
}

if (password.length < 6) {
return NextResponse.json(
{ message: 'Password must be at least 6 characters' },
{ status: 400 },
return addHeaders(

Check failure on line 26 in src/app/api/auth/signup/route.ts

View workflow job for this annotation

GitHub Actions / type-check

Type 'Response' is missing the following properties from type 'NextResponse<AuthResponse | { message: string; }>': cookies, [INTERNALS]
NextResponse.json({ message: 'Password must be at least 6 characters' }, { status: 400 }),
);
}

// Mock: Check if email already exists (demo only)
if (email === 'existing@teachlink.com') {
return NextResponse.json({ message: 'Email already registered' }, { status: 409 });
return addHeaders(NextResponse.json({ message: 'Email already registered' }, { status: 409 }));
}

// Simulate successful signup
return NextResponse.json(
{
message: 'Account created successfully',
user: {
id: Math.random().toString(36).substr(2, 9),
name: name,
email: email,
return addHeaders(
NextResponse.json(
{
message: 'Account created successfully',
user: {
id: Math.random().toString(36).substr(2, 9),
name: name,
email: email,
},
token: 'mock-jwt-token-' + Date.now(),
},
token: 'mock-jwt-token-' + Date.now(),
},
{ status: 201 },
{ status: 201 },
),
);
} catch (error) {
console.error('Signup error:', error);
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
return addHeaders(NextResponse.json({ message: 'Internal server error' }, { status: 500 }));
}
}
45 changes: 34 additions & 11 deletions src/app/api/bookmarks/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server';
import type { VideoBookmark, ApiResponse, SuccessResponse } from '@/types/api';
import { withRateLimit } from '@/lib/ratelimit';

type PersistedVideoBookmark = VideoBookmark;

Expand All @@ -13,31 +14,43 @@ const keyFor = (userId: string | undefined, lessonId: string) => {
export async function GET(
request: Request,
): Promise<NextResponse<ApiResponse<PersistedVideoBookmark[]> | SuccessResponse>> {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'WRITE');
if (rateLimitResponse) {
return rateLimitResponse as NextResponse;
}

const { searchParams } = new URL(request.url);
const lessonId = searchParams.get('lessonId');
const userId = searchParams.get('userId') ?? undefined;

if (!lessonId) {
return NextResponse.json({ success: false, message: 'lessonId is required' }, { status: 400 });
return addHeaders(NextResponse.json({ success: false, message: 'lessonId is required' }, { status: 400 }));
}

return NextResponse.json({
data: bookmarksStore.get(keyFor(userId, lessonId)) ?? [],
success: true,
});
return addHeaders(
NextResponse.json({
data: bookmarksStore.get(keyFor(userId, lessonId)) ?? [],
success: true,
}),
);
}

export async function POST(
request: Request,
): Promise<NextResponse<ApiResponse<PersistedVideoBookmark> | SuccessResponse>> {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'WRITE');
if (rateLimitResponse) {
return rateLimitResponse as NextResponse;
}

const body = (await request.json()) as {
userId?: string;
lessonId: string;
bookmark: { id?: string; time: number; title: string; note?: string };
};

if (!body?.lessonId || !body?.bookmark?.time || !body?.bookmark?.title) {
return NextResponse.json({ success: false, message: 'Invalid payload' }, { status: 400 });
return addHeaders(NextResponse.json({ success: false, message: 'Invalid payload' }, { status: 400 }));
}

const now = new Date().toISOString();
Expand All @@ -57,10 +70,15 @@ export async function POST(
const next = [persisted, ...prev.filter((b) => b.id !== persisted.id)];
bookmarksStore.set(key, next);

return NextResponse.json({ success: true, data: persisted });
return addHeaders(NextResponse.json({ success: true, data: persisted }));
}

export async function PATCH(request: Request): Promise<NextResponse<SuccessResponse>> {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'WRITE');
if (rateLimitResponse) {
return rateLimitResponse as NextResponse;
}

const body = (await request.json()) as {
userId?: string;
lessonId: string;
Expand All @@ -71,7 +89,7 @@ export async function PATCH(request: Request): Promise<NextResponse<SuccessRespo
};

if (!body?.lessonId || !body?.id || !body?.title) {
return NextResponse.json({ success: false, message: 'Invalid payload' }, { status: 400 });
return addHeaders(NextResponse.json({ success: false, message: 'Invalid payload' }, { status: 400 }));
}

const key = keyFor(body.userId, body.lessonId);
Expand All @@ -91,13 +109,18 @@ export async function PATCH(request: Request): Promise<NextResponse<SuccessRespo
);

bookmarksStore.set(key, next);
return NextResponse.json({ success: true });
return addHeaders(NextResponse.json({ success: true }));
}

export async function DELETE(request: Request): Promise<NextResponse<SuccessResponse>> {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'WRITE');
if (rateLimitResponse) {
return rateLimitResponse as NextResponse;
}

const body = (await request.json()) as { userId?: string; lessonId: string; id: string };
if (!body?.lessonId || !body?.id) {
return NextResponse.json({ success: false, message: 'Invalid payload' }, { status: 400 });
return addHeaders(NextResponse.json({ success: false, message: 'Invalid payload' }, { status: 400 }));
}

const key = keyFor(body.userId, body.lessonId);
Expand All @@ -107,5 +130,5 @@ export async function DELETE(request: Request): Promise<NextResponse<SuccessResp
prev.filter((b) => b.id !== body.id),
);

return NextResponse.json({ success: true });
return addHeaders(NextResponse.json({ success: true }));
}
19 changes: 13 additions & 6 deletions src/app/api/courses/[id]/lessons/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/ratelimit';

export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'READ');
if (rateLimitResponse) {
return rateLimitResponse;
}

export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) {
await params;
// Mock lessons data
const lessons = [
{
id: '1',
Expand Down Expand Up @@ -30,8 +35,10 @@ export async function GET(_request: Request, { params }: { params: Promise<{ id:
},
];

return NextResponse.json({
data: lessons,
success: true,
});
return addHeaders(
NextResponse.json({
data: lessons,
success: true,
}),
);
}
19 changes: 13 additions & 6 deletions src/app/api/courses/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { NextResponse } from 'next/server';
import type { Course, ApiResponse } from '@/types/api';
import { withRateLimit } from '@/lib/ratelimit';

export async function GET(
_request: Request,
request: Request,
{ params }: { params: Promise<{ id: string }> },
): Promise<NextResponse<ApiResponse<Course>>> {
const { addHeaders, rateLimitResponse } = withRateLimit(request, 'READ');
if (rateLimitResponse) {
return rateLimitResponse as NextResponse<ApiResponse<Course>>;
}

const { id } = await params;
// Mock single course data
const course = {
id,
title: 'Web3 UX Design Principles',
Expand All @@ -21,8 +26,10 @@ export async function GET(
downloaded: false,
};

return NextResponse.json({
data: course,
success: true,
});
return addHeaders(
NextResponse.json({
data: course,
success: true,
}),
);
}
18 changes: 13 additions & 5 deletions src/app/api/courses/downloadable/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { NextResponse } from 'next/server';
import { withRateLimit } from '@/lib/ratelimit';

export async function GET() {
// Mock downloadable courses data
const mockRequest = new Request('http://localhost');
const { addHeaders, rateLimitResponse } = withRateLimit(mockRequest, 'READ');
if (rateLimitResponse) {
return rateLimitResponse;
}

const courses = [
{
id: '1',
Expand Down Expand Up @@ -31,8 +37,10 @@ export async function GET() {
},
];

return NextResponse.json({
data: courses,
success: true,
});
return addHeaders(
NextResponse.json({
data: courses,
success: true,
}),
);
}
Loading
Loading