Skip to content

Commit 1a46f49

Browse files
feat: more pages and dsoc
feat: more pages and dsoc
2 parents 0327376 + 9b90587 commit 1a46f49

72 files changed

Lines changed: 10227 additions & 59 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/admin/dsoc/page.tsx

Lines changed: 423 additions & 0 deletions
Large diffs are not rendered by default.

app/admin/dsoc/projects/new/page.tsx

Lines changed: 410 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import connectDB from '@/lib/db';
3+
import { DSOCApplication } from '@/models/DSOCApplication';
4+
import { DSOCProject } from '@/models/DSOCProject';
5+
import { DSOCMentee } from '@/models/DSOCMentee';
6+
7+
// GET single application
8+
export async function GET(
9+
request: NextRequest,
10+
{ params }: { params: Promise<{ id: string }> }
11+
) {
12+
try {
13+
await connectDB();
14+
const { id } = await params;
15+
16+
const application = await DSOCApplication.findById(id)
17+
.populate('project', 'title organization status mentors')
18+
.populate('mentee', 'name email picture university github linkedin')
19+
.lean();
20+
21+
if (!application) {
22+
return NextResponse.json(
23+
{ success: false, error: 'Application not found' },
24+
{ status: 404 }
25+
);
26+
}
27+
28+
return NextResponse.json({
29+
success: true,
30+
data: application,
31+
});
32+
} catch (error) {
33+
console.error('Error fetching application:', error);
34+
return NextResponse.json(
35+
{ success: false, error: 'Failed to fetch application' },
36+
{ status: 500 }
37+
);
38+
}
39+
}
40+
41+
// PUT update application status (mentor/admin)
42+
export async function PUT(
43+
request: NextRequest,
44+
{ params }: { params: Promise<{ id: string }> }
45+
) {
46+
try {
47+
await connectDB();
48+
const { id } = await params;
49+
50+
// TODO: Add mentor/admin authentication check
51+
const body = await request.json();
52+
const { status, mentorNotes, adminNotes, score } = body;
53+
54+
const application = await DSOCApplication.findById(id);
55+
56+
if (!application) {
57+
return NextResponse.json(
58+
{ success: false, error: 'Application not found' },
59+
{ status: 404 }
60+
);
61+
}
62+
63+
// Update application
64+
if (status) application.status = status;
65+
if (mentorNotes !== undefined) application.mentorNotes = mentorNotes;
66+
if (adminNotes !== undefined) application.adminNotes = adminNotes;
67+
if (score !== undefined) application.score = score;
68+
69+
if (status && status !== application.status) {
70+
application.reviewedAt = new Date();
71+
}
72+
73+
await application.save();
74+
75+
// If accepted, add mentee to project
76+
if (status === 'accepted') {
77+
await DSOCProject.findByIdAndUpdate(
78+
application.project,
79+
{ $addToSet: { selectedMentees: application.mentee } }
80+
);
81+
82+
await DSOCMentee.findByIdAndUpdate(
83+
application.mentee,
84+
{ $addToSet: { projects: application.project } }
85+
);
86+
}
87+
88+
return NextResponse.json({
89+
success: true,
90+
data: application,
91+
});
92+
} catch (error) {
93+
console.error('Error updating application:', error);
94+
return NextResponse.json(
95+
{ success: false, error: 'Failed to update application' },
96+
{ status: 500 }
97+
);
98+
}
99+
}
100+
101+
// DELETE withdraw application
102+
export async function DELETE(
103+
request: NextRequest,
104+
{ params }: { params: Promise<{ id: string }> }
105+
) {
106+
try {
107+
await connectDB();
108+
const { id } = await params;
109+
110+
const application = await DSOCApplication.findById(id);
111+
112+
if (!application) {
113+
return NextResponse.json(
114+
{ success: false, error: 'Application not found' },
115+
{ status: 404 }
116+
);
117+
}
118+
119+
// Can only withdraw pending applications
120+
if (application.status !== 'pending') {
121+
return NextResponse.json(
122+
{ success: false, error: 'Cannot withdraw a processed application' },
123+
{ status: 400 }
124+
);
125+
}
126+
127+
application.status = 'withdrawn';
128+
await application.save();
129+
130+
return NextResponse.json({
131+
success: true,
132+
message: 'Application withdrawn successfully',
133+
});
134+
} catch (error) {
135+
console.error('Error withdrawing application:', error);
136+
return NextResponse.json(
137+
{ success: false, error: 'Failed to withdraw application' },
138+
{ status: 500 }
139+
);
140+
}
141+
}

app/api/dsoc/applications/route.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import connectDB from '@/lib/db';
3+
import { DSOCApplication } from '@/models/DSOCApplication';
4+
import { DSOCProject } from '@/models/DSOCProject';
5+
import jwt from 'jsonwebtoken';
6+
7+
// Helper to get mentee from token
8+
async function getMenteeFromToken(request: NextRequest) {
9+
const token = request.cookies.get('dsoc-mentee-token')?.value;
10+
if (!token) return null;
11+
12+
try {
13+
const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as { id: string; role: string };
14+
if (decoded.role !== 'dsoc-mentee') return null;
15+
return decoded.id;
16+
} catch {
17+
return null;
18+
}
19+
}
20+
21+
// GET all applications (with filters)
22+
export async function GET(request: NextRequest) {
23+
try {
24+
await connectDB();
25+
26+
const searchParams = request.nextUrl.searchParams;
27+
const projectId = searchParams.get('project');
28+
const status = searchParams.get('status');
29+
const menteeId = await getMenteeFromToken(request);
30+
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
32+
const query: any = {};
33+
34+
if (projectId) query.project = projectId;
35+
if (status) query.status = status;
36+
if (menteeId && searchParams.get('my') === 'true') {
37+
query.mentee = menteeId;
38+
}
39+
40+
const applications = await DSOCApplication.find(query)
41+
.populate('project', 'title organization status')
42+
.populate('mentee', 'name email picture university')
43+
.sort({ createdAt: -1 })
44+
.lean();
45+
46+
return NextResponse.json({
47+
success: true,
48+
data: applications,
49+
});
50+
} catch (error) {
51+
console.error('Error fetching applications:', error);
52+
return NextResponse.json(
53+
{ success: false, error: 'Failed to fetch applications' },
54+
{ status: 500 }
55+
);
56+
}
57+
}
58+
59+
// POST create new application
60+
export async function POST(request: NextRequest) {
61+
try {
62+
await connectDB();
63+
64+
const menteeId = await getMenteeFromToken(request);
65+
66+
if (!menteeId) {
67+
return NextResponse.json(
68+
{ success: false, error: 'Please login to apply' },
69+
{ status: 401 }
70+
);
71+
}
72+
73+
const body = await request.json();
74+
const { projectId, ...applicationData } = body;
75+
76+
// Check if project exists and is open
77+
const project = await DSOCProject.findById(projectId);
78+
79+
if (!project) {
80+
return NextResponse.json(
81+
{ success: false, error: 'Project not found' },
82+
{ status: 404 }
83+
);
84+
}
85+
86+
if (project.status !== 'open') {
87+
return NextResponse.json(
88+
{ success: false, error: 'This project is not accepting applications' },
89+
{ status: 400 }
90+
);
91+
}
92+
93+
if (new Date() > new Date(project.applicationDeadline)) {
94+
return NextResponse.json(
95+
{ success: false, error: 'Application deadline has passed' },
96+
{ status: 400 }
97+
);
98+
}
99+
100+
// Check for existing application
101+
const existingApplication = await DSOCApplication.findOne({
102+
project: projectId,
103+
mentee: menteeId,
104+
});
105+
106+
if (existingApplication) {
107+
return NextResponse.json(
108+
{ success: false, error: 'You have already applied to this project' },
109+
{ status: 400 }
110+
);
111+
}
112+
113+
const application = new DSOCApplication({
114+
project: projectId,
115+
mentee: menteeId,
116+
...applicationData,
117+
});
118+
119+
await application.save();
120+
121+
return NextResponse.json({
122+
success: true,
123+
data: application,
124+
message: 'Application submitted successfully!',
125+
}, { status: 201 });
126+
} catch (error) {
127+
console.error('Error creating application:', error);
128+
return NextResponse.json(
129+
{ success: false, error: 'Failed to submit application' },
130+
{ status: 500 }
131+
);
132+
}
133+
}

app/api/dsoc/mentee/login/route.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import connectDB from '@/lib/db';
3+
import { DSOCMentee } from '@/models/DSOCMentee';
4+
import jwt from 'jsonwebtoken';
5+
6+
// POST - Login mentee
7+
export async function POST(request: NextRequest) {
8+
try {
9+
await connectDB();
10+
11+
const { username, password } = await request.json();
12+
13+
if (!username || !password) {
14+
return NextResponse.json(
15+
{ success: false, error: 'Username and password are required' },
16+
{ status: 400 }
17+
);
18+
}
19+
20+
const mentee = await DSOCMentee.findOne({
21+
$or: [{ username }, { email: username }]
22+
}).select('+password');
23+
24+
if (!mentee) {
25+
return NextResponse.json(
26+
{ success: false, error: 'Invalid credentials' },
27+
{ status: 401 }
28+
);
29+
}
30+
31+
const isMatch = await mentee.comparePassword(password);
32+
33+
if (!isMatch) {
34+
return NextResponse.json(
35+
{ success: false, error: 'Invalid credentials' },
36+
{ status: 401 }
37+
);
38+
}
39+
40+
if (!mentee.isActive) {
41+
return NextResponse.json(
42+
{ success: false, error: 'Account is deactivated' },
43+
{ status: 403 }
44+
);
45+
}
46+
47+
// Generate JWT token
48+
const token = jwt.sign(
49+
{ id: mentee._id, role: 'dsoc-mentee' },
50+
process.env.JWT_SECRET as string,
51+
{ expiresIn: '7d' }
52+
);
53+
54+
const response = NextResponse.json({
55+
success: true,
56+
data: {
57+
id: mentee._id,
58+
name: mentee.name,
59+
email: mentee.email,
60+
username: mentee.username,
61+
},
62+
});
63+
64+
// Set cookie
65+
response.cookies.set('dsoc-mentee-token', token, {
66+
httpOnly: true,
67+
secure: process.env.NODE_ENV === 'production',
68+
sameSite: 'lax',
69+
maxAge: 7 * 24 * 60 * 60, // 7 days
70+
});
71+
72+
return response;
73+
} catch (error) {
74+
console.error('Error logging in DSOC mentee:', error);
75+
return NextResponse.json(
76+
{ success: false, error: 'Failed to login' },
77+
{ status: 500 }
78+
);
79+
}
80+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { NextResponse } from 'next/server';
2+
3+
export async function POST() {
4+
const response = NextResponse.json({
5+
success: true,
6+
message: 'Logged out successfully',
7+
});
8+
9+
response.cookies.set('dsoc-mentee-token', '', {
10+
httpOnly: true,
11+
secure: process.env.NODE_ENV === 'production',
12+
sameSite: 'lax',
13+
maxAge: 0,
14+
});
15+
16+
return response;
17+
}

0 commit comments

Comments
 (0)