Skip to content

Commit d4e86f8

Browse files
committed
Merge upstream/main into fix/706-ci-test-timeout
Resolves conflicts in: - src/app/api/auth/login/route.ts - src/app/api/performance/vitals/route.ts - src/app/components/dashboard/DashboardGrid.tsx - src/app/hooks/useDashboardWidgets.tsx - src/components/CommandPalette.tsx - src/store/synchronizationEngine.ts All conflicts resolved by accepting upstream/main's fixes from #836 (orphaned merge-conflict artifacts) plus reconciling missing imports.
2 parents 05a9f8c + 3227421 commit d4e86f8

20 files changed

Lines changed: 1274 additions & 920 deletions

File tree

package-lock.json

Lines changed: 1031 additions & 811 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"migrate": "npx tsx src/lib/db/migrate.ts"
4040
},
4141
"dependencies": {
42+
"bcryptjs": "^2.4.3",
4243
"@apollo/client": "^3.8.0",
4344
"@dnd-kit/core": "^6.3.1",
4445
"@dnd-kit/sortable": "^8.0.0",
@@ -109,6 +110,7 @@
109110
"@testing-library/jest-dom": "^6.9.1",
110111
"@testing-library/react": "^16.3.0",
111112
"@testing-library/user-event": "^14.6.1",
113+
"@types/bcryptjs": "^2.4.6",
112114
"@types/chai": "^5.2.3",
113115
"@types/d3-array": "^3.2.2",
114116
"@types/d3-color": "^3.1.3",

pnpm-lock.yaml

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/sitemap.xml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,85 +3,85 @@
33

44
<url>
55
<loc>https://teachlink.app</loc>
6-
<lastmod>2026-06-29</lastmod>
6+
<lastmod>2026-07-01</lastmod>
77
<changefreq>daily</changefreq>
88
<priority>1.0</priority>
99
</url>
1010
<url>
1111
<loc>https://teachlink.app/search</loc>
12-
<lastmod>2026-06-29</lastmod>
12+
<lastmod>2026-07-01</lastmod>
1313
<changefreq>weekly</changefreq>
1414
<priority>0.8</priority>
1515
</url>
1616
<url>
1717
<loc>https://teachlink.app/study-groups</loc>
18-
<lastmod>2026-06-29</lastmod>
18+
<lastmod>2026-07-01</lastmod>
1919
<changefreq>weekly</changefreq>
2020
<priority>0.7</priority>
2121
</url>
2222
<url>
2323
<loc>https://teachlink.app/leaderboard</loc>
24-
<lastmod>2026-06-29</lastmod>
24+
<lastmod>2026-07-01</lastmod>
2525
<changefreq>weekly</changefreq>
2626
<priority>0.6</priority>
2727
</url>
2828
<url>
2929
<loc>https://teachlink.app/certificates</loc>
30-
<lastmod>2026-06-29</lastmod>
30+
<lastmod>2026-07-01</lastmod>
3131
<changefreq>weekly</changefreq>
3232
<priority>0.5</priority>
3333
</url>
3434
<url>
3535
<loc>https://teachlink.app/support</loc>
36-
<lastmod>2026-06-29</lastmod>
36+
<lastmod>2026-07-01</lastmod>
3737
<changefreq>monthly</changefreq>
3838
<priority>0.4</priority>
3939
</url>
4040
<url>
4141
<loc>https://teachlink.app/privacy</loc>
42-
<lastmod>2026-06-29</lastmod>
42+
<lastmod>2026-07-01</lastmod>
4343
<changefreq>monthly</changefreq>
4444
<priority>0.3</priority>
4545
</url>
4646
<url>
4747
<loc>https://teachlink.app/release-notes</loc>
48-
<lastmod>2026-06-29</lastmod>
48+
<lastmod>2026-07-01</lastmod>
4949
<changefreq>monthly</changefreq>
5050
<priority>0.3</priority>
5151
</url>
5252
<url>
5353
<loc>https://teachlink.app/courses/1</loc>
54-
<lastmod>2026-06-29</lastmod>
54+
<lastmod>2026-07-01</lastmod>
5555
<changefreq>weekly</changefreq>
5656
<priority>0.8</priority>
5757
</url>
5858
<url>
5959
<loc>https://teachlink.app/courses/2</loc>
60-
<lastmod>2026-06-29</lastmod>
60+
<lastmod>2026-07-01</lastmod>
6161
<changefreq>weekly</changefreq>
6262
<priority>0.8</priority>
6363
</url>
6464
<url>
6565
<loc>https://teachlink.app/courses/3</loc>
66-
<lastmod>2026-06-29</lastmod>
66+
<lastmod>2026-07-01</lastmod>
6767
<changefreq>weekly</changefreq>
6868
<priority>0.8</priority>
6969
</url>
7070
<url>
7171
<loc>https://teachlink.app/courses/4</loc>
72-
<lastmod>2026-06-29</lastmod>
72+
<lastmod>2026-07-01</lastmod>
7373
<changefreq>weekly</changefreq>
7474
<priority>0.8</priority>
7575
</url>
7676
<url>
7777
<loc>https://teachlink.app/courses/5</loc>
78-
<lastmod>2026-06-29</lastmod>
78+
<lastmod>2026-07-01</lastmod>
7979
<changefreq>weekly</changefreq>
8080
<priority>0.8</priority>
8181
</url>
8282
<url>
8383
<loc>https://teachlink.app/courses/6</loc>
84-
<lastmod>2026-06-29</lastmod>
84+
<lastmod>2026-07-01</lastmod>
8585
<changefreq>weekly</changefreq>
8686
<priority>0.8</priority>
8787
</url>

src/app/api/auth/__tests__/email-verification-routes.test.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { describe, expect, it, beforeEach, vi } from 'vitest';
22
import { GET as verifyGET, POST as verifyPOST } from '../email-verification/verify/route';
33
import { POST as resendPOST } from '../email-verification/resend/route';
44
import { POST as restorePOST } from '../email-verification/restore/route';
5-
import { POST as signupPOST } from '../../signup/route';
6-
import { POST as loginPOST } from '../../login/route';
5+
import { POST as signupPOST } from '../signup/route';
6+
import { POST as loginPOST } from '../login/route';
77

88
vi.mock('@/lib/ratelimit', () => ({
99
withRateLimit: vi.fn(() => ({
@@ -43,6 +43,20 @@ vi.mock('@/lib/auth/email-verification', () => ({
4343
getVerificationTokenTtlMinutes: vi.fn(() => 15),
4444
}));
4545

46+
vi.mock('bcryptjs', () => ({
47+
default: {
48+
compare: vi.fn().mockResolvedValue(true),
49+
},
50+
}));
51+
52+
vi.mock('@/lib/db/pool', async (importOriginal) => {
53+
const actual = await importOriginal<typeof import('@/lib/db/pool')>();
54+
return {
55+
...actual,
56+
findUserByEmail: vi.fn(),
57+
};
58+
});
59+
4660
describe('email verification routes', () => {
4761
beforeEach(() => {
4862
vi.clearAllMocks();
@@ -94,6 +108,12 @@ describe('email verification routes', () => {
94108

95109
it('blocks login until verification is complete', async () => {
96110
const { getVerificationStatus } = await import('@/lib/auth/email-verification');
111+
const { findUserByEmail } = await import('@/lib/db/pool');
112+
vi.mocked(findUserByEmail).mockResolvedValue({
113+
id: 'user-1',
114+
password_hash: 'hashed-password',
115+
role: 'STUDENT',
116+
});
97117
vi.mocked(getVerificationStatus).mockResolvedValue({
98118
required: true,
99119
status: 'pending',

src/app/api/auth/login/route.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextRequest, NextResponse } from 'next/server';
2+
import bcrypt from 'bcryptjs';
23
import { withRateLimit } from '@/lib/ratelimit';
34
import { validateBody } from '@/lib/validation';
45
import { LoginRequestSchema } from '@/types/api/auth.dto';
@@ -10,8 +11,10 @@ import { createLogger } from '@/lib/logging';
1011
const logger = createLogger('api-auth-login');
1112
import { signToken } from '@/lib/auth/jwt';
1213
import { UserRole } from '@/types/api';
14+
import { findUserByEmail, TIMING_SAFE_DUMMY_HASH } from '@/lib/db/pool';
1315

1416
export const runtime = 'nodejs';
17+
export const dynamic = 'force-dynamic';
1518

1619
// ---------------------------------------------------------------------------
1720
// POST /api/auth/login
@@ -30,17 +33,16 @@ export async function POST(
3033
if (!result.ok) return addHeaders(result.error) as NextResponse;
3134

3235
const { email, password } = result.data;
33-
const verification = await getVerificationStatus(email);
36+
const user = await findUserByEmail(email);
37+
const passwordHash = user?.password_hash ?? TIMING_SAFE_DUMMY_HASH;
38+
const credentialsMatch = await bcrypt.compare(password, passwordHash);
3439

35-
// Mock: demo credentials
36-
if (email === 'demo@teachlink.com' && password === 'password123') {
37-
const user = { id: '1', name: 'Demo User', email };
38-
const token = await signToken({ sub: user.id, role: UserRole.STUDENT });
39-
return addHeaders(
40-
NextResponse.json({ message: 'Login successful', user, token }, { status: 200 }),
41-
);
40+
if (!user || !credentialsMatch) {
41+
return addHeaders(NextResponse.json({ message: 'Invalid credentials' }, { status: 401 }));
4242
}
4343

44+
const verification = await getVerificationStatus(email);
45+
4446
if (verification && verification.required && verification.status !== 'verified') {
4547
return addHeaders(
4648
NextResponse.json(
@@ -50,20 +52,26 @@ export async function POST(
5052
);
5153
}
5254

53-
// Mock: accept any valid email + password >= 6 chars
54-
if (password.length >= 6) {
55-
const user = {
56-
id: Math.random().toString(36).substring(2, 9),
57-
name: email.split('@')[0],
58-
email,
59-
};
60-
const token = await signToken({ sub: user.id, role: UserRole.STUDENT });
61-
return addHeaders(
62-
NextResponse.json({ message: 'Login successful', user, token }, { status: 200 }),
63-
);
64-
}
55+
const role = Object.values(UserRole).includes(user.role as UserRole)
56+
? (user.role as UserRole)
57+
: UserRole.STUDENT;
58+
const token = await signToken({ sub: user.id, role, email });
6559

66-
return addHeaders(NextResponse.json({ message: 'Invalid email or password' }, { status: 401 }));
60+
return addHeaders(
61+
NextResponse.json(
62+
{
63+
message: 'Login successful',
64+
user: {
65+
id: user.id,
66+
name: email.split('@')[0],
67+
email,
68+
role: user.role,
69+
},
70+
token,
71+
},
72+
{ status: 200 },
73+
),
74+
);
6775
} catch (error) {
6876
logger.error('Login error', { error });
6977

src/app/api/auth/signup/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import {
1010
buildVerificationMailContext,
1111
createOrRestoreVerification,
1212
} from '@/lib/auth/email-verification';
13+
import {
14+
generateReferralCode,
15+
getReferralCodeOwner,
16+
referralCodeExists,
17+
storeReferralCode,
18+
validateReferralCode,
19+
} from '@/lib/referral';
1320

1421
export const runtime = 'nodejs';
1522

@@ -116,6 +123,8 @@ export async function POST(
116123
}
117124

118125
const userId = randomUUID();
126+
const userReferralCode = generateReferralCode();
127+
storeReferralCode(email, userReferralCode);
119128
edgeLog('info', route, 'Account created', {
120129
userId,
121130
verificationId: verificationResult.record.verificationId,

0 commit comments

Comments
 (0)