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
423 changes: 423 additions & 0 deletions app/admin/dsoc/page.tsx

Large diffs are not rendered by default.

410 changes: 410 additions & 0 deletions app/admin/dsoc/projects/new/page.tsx

Large diffs are not rendered by default.

141 changes: 141 additions & 0 deletions app/api/dsoc/applications/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { NextRequest, NextResponse } from 'next/server';
import connectDB from '@/lib/db';
import { DSOCApplication } from '@/models/DSOCApplication';
import { DSOCProject } from '@/models/DSOCProject';
import { DSOCMentee } from '@/models/DSOCMentee';

// GET single application
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
await connectDB();
const { id } = await params;

const application = await DSOCApplication.findById(id)
.populate('project', 'title organization status mentors')
.populate('mentee', 'name email picture university github linkedin')
.lean();

if (!application) {
return NextResponse.json(
{ success: false, error: 'Application not found' },
{ status: 404 }
);
}

return NextResponse.json({
success: true,
data: application,
});
} catch (error) {
console.error('Error fetching application:', error);
return NextResponse.json(
{ success: false, error: 'Failed to fetch application' },
{ status: 500 }
);
}
}

// PUT update application status (mentor/admin)
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
await connectDB();
const { id } = await params;

// TODO: Add mentor/admin authentication check
const body = await request.json();
const { status, mentorNotes, adminNotes, score } = body;

const application = await DSOCApplication.findById(id);

if (!application) {
return NextResponse.json(
{ success: false, error: 'Application not found' },
{ status: 404 }
);
}

// Update application
if (status) application.status = status;
if (mentorNotes !== undefined) application.mentorNotes = mentorNotes;
if (adminNotes !== undefined) application.adminNotes = adminNotes;
if (score !== undefined) application.score = score;

if (status && status !== application.status) {
application.reviewedAt = new Date();
}

await application.save();

// If accepted, add mentee to project
if (status === 'accepted') {
await DSOCProject.findByIdAndUpdate(
application.project,
{ $addToSet: { selectedMentees: application.mentee } }
);

await DSOCMentee.findByIdAndUpdate(
application.mentee,
{ $addToSet: { projects: application.project } }
);
}

return NextResponse.json({
success: true,
data: application,
});
} catch (error) {
console.error('Error updating application:', error);
return NextResponse.json(
{ success: false, error: 'Failed to update application' },
{ status: 500 }
);
}
}

// DELETE withdraw application
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
await connectDB();
const { id } = await params;

const application = await DSOCApplication.findById(id);

if (!application) {
return NextResponse.json(
{ success: false, error: 'Application not found' },
{ status: 404 }
);
}

// Can only withdraw pending applications
if (application.status !== 'pending') {
return NextResponse.json(
{ success: false, error: 'Cannot withdraw a processed application' },
{ status: 400 }
);
}

application.status = 'withdrawn';
await application.save();

return NextResponse.json({
success: true,
message: 'Application withdrawn successfully',
});
} catch (error) {
console.error('Error withdrawing application:', error);
return NextResponse.json(
{ success: false, error: 'Failed to withdraw application' },
{ status: 500 }
);
}
}
133 changes: 133 additions & 0 deletions app/api/dsoc/applications/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { NextRequest, NextResponse } from 'next/server';
import connectDB from '@/lib/db';
import { DSOCApplication } from '@/models/DSOCApplication';
import { DSOCProject } from '@/models/DSOCProject';
import jwt from 'jsonwebtoken';

// Helper to get mentee from token
async function getMenteeFromToken(request: NextRequest) {
const token = request.cookies.get('dsoc-mentee-token')?.value;
if (!token) return null;

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as { id: string; role: string };
if (decoded.role !== 'dsoc-mentee') return null;
return decoded.id;
} catch {
return null;
}
}

// GET all applications (with filters)
export async function GET(request: NextRequest) {
try {
await connectDB();

const searchParams = request.nextUrl.searchParams;
const projectId = searchParams.get('project');
const status = searchParams.get('status');
const menteeId = await getMenteeFromToken(request);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const query: any = {};

if (projectId) query.project = projectId;
if (status) query.status = status;
if (menteeId && searchParams.get('my') === 'true') {
query.mentee = menteeId;
}

const applications = await DSOCApplication.find(query)
.populate('project', 'title organization status')
.populate('mentee', 'name email picture university')
.sort({ createdAt: -1 })
.lean();

return NextResponse.json({
success: true,
data: applications,
});
} catch (error) {
console.error('Error fetching applications:', error);
return NextResponse.json(
{ success: false, error: 'Failed to fetch applications' },
{ status: 500 }
);
}
}

// POST create new application
export async function POST(request: NextRequest) {
try {
await connectDB();

const menteeId = await getMenteeFromToken(request);

if (!menteeId) {
return NextResponse.json(
{ success: false, error: 'Please login to apply' },
{ status: 401 }
);
}

const body = await request.json();
const { projectId, ...applicationData } = body;

// Check if project exists and is open
const project = await DSOCProject.findById(projectId);

if (!project) {
return NextResponse.json(
{ success: false, error: 'Project not found' },
{ status: 404 }
);
}

if (project.status !== 'open') {
return NextResponse.json(
{ success: false, error: 'This project is not accepting applications' },
{ status: 400 }
);
}

if (new Date() > new Date(project.applicationDeadline)) {
return NextResponse.json(
{ success: false, error: 'Application deadline has passed' },
{ status: 400 }
);
}

// Check for existing application
const existingApplication = await DSOCApplication.findOne({
project: projectId,
mentee: menteeId,
});

if (existingApplication) {
return NextResponse.json(
{ success: false, error: 'You have already applied to this project' },
{ status: 400 }
);
}

const application = new DSOCApplication({
project: projectId,
mentee: menteeId,
...applicationData,
});

await application.save();

return NextResponse.json({
success: true,
data: application,
message: 'Application submitted successfully!',
}, { status: 201 });
} catch (error) {
console.error('Error creating application:', error);
return NextResponse.json(
{ success: false, error: 'Failed to submit application' },
{ status: 500 }
);
}
}
80 changes: 80 additions & 0 deletions app/api/dsoc/mentee/login/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { NextRequest, NextResponse } from 'next/server';
import connectDB from '@/lib/db';
import { DSOCMentee } from '@/models/DSOCMentee';
import jwt from 'jsonwebtoken';

// POST - Login mentee
export async function POST(request: NextRequest) {
try {
await connectDB();

const { username, password } = await request.json();

if (!username || !password) {
return NextResponse.json(
{ success: false, error: 'Username and password are required' },
{ status: 400 }
);
}

const mentee = await DSOCMentee.findOne({
$or: [{ username }, { email: username }]
}).select('+password');

if (!mentee) {
return NextResponse.json(
{ success: false, error: 'Invalid credentials' },
{ status: 401 }
);
}

const isMatch = await mentee.comparePassword(password);

if (!isMatch) {
return NextResponse.json(
{ success: false, error: 'Invalid credentials' },
{ status: 401 }
);
}

if (!mentee.isActive) {
return NextResponse.json(
{ success: false, error: 'Account is deactivated' },
{ status: 403 }
);
}

// Generate JWT token
const token = jwt.sign(
{ id: mentee._id, role: 'dsoc-mentee' },
process.env.JWT_SECRET as string,
{ expiresIn: '7d' }
);

const response = NextResponse.json({
success: true,
data: {
id: mentee._id,
name: mentee.name,
email: mentee.email,
username: mentee.username,
},
});

// Set cookie
response.cookies.set('dsoc-mentee-token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60, // 7 days
});

return response;
} catch (error) {
console.error('Error logging in DSOC mentee:', error);
return NextResponse.json(
{ success: false, error: 'Failed to login' },
{ status: 500 }
);
}
}
17 changes: 17 additions & 0 deletions app/api/dsoc/mentee/logout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NextResponse } from 'next/server';

export async function POST() {
const response = NextResponse.json({
success: true,
message: 'Logged out successfully',
});

response.cookies.set('dsoc-mentee-token', '', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 0,
});

return response;
}
Loading
Loading