Skip to content
This repository was archived by the owner on Apr 21, 2026. It is now read-only.
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
13 changes: 11 additions & 2 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,10 @@ export const opts: NextAuthOptions = {
callbacks: {
async session({ session, user }) {
// With database strategy, we get the fresh user data on every request
// Check if user is an island attendee
const { isAttendee } = await import('@/lib/userTags');
const isAttendeeFlag = await isAttendee(user.id);

return {
...session,
user: {
Expand All @@ -293,7 +297,8 @@ export const opts: NextAuthOptions = {
role: user.role,
isAdmin: user.isAdmin,
status: user.status,
emailVerified: user.emailVerified
emailVerified: user.emailVerified,
isAttendee: isAttendeeFlag
}
};
},
Expand Down Expand Up @@ -338,7 +343,11 @@ export const opts: NextAuthOptions = {
console.log('Redirecting to:', url);
return url;
}
return `${baseUrl}/bay`;

// Clear experience mode cookie on login so it gets reset based on user status
// Note: We need to add a flag to clear this on the client side since we can't
// access cookies directly in the redirect callback
return `${baseUrl}/bay?clearExperience=true`;
}
},
pages: {
Expand Down
40 changes: 40 additions & 0 deletions app/api/gallery/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,47 @@ export async function GET(request: Request) {

console.log('Fetching all projects for gallery...');

// Check if user is admin to determine tag exposure
const isAdmin = session.user.role === 'Admin' || session.user.isAdmin === true;

// Get experience mode from query params for server-side filtering
const { searchParams } = new URL(request.url);
const isIslandMode = searchParams.get('isIslandMode') === 'true';

// Build where clause for server-side filtering based on experience mode
let whereClause: any = {};

// ALL USERS (including admins) get filtered based on experience mode in gallery
if (isIslandMode) {
// Island mode: only show projects with 'island-project' tag
whereClause = {
projectTags: {
some: {
tag: {
name: 'island-project'
}
}
}
};
} else {
// Voyage mode: only show projects WITHOUT 'island-project' tag
whereClause = {
NOT: {
projectTags: {
some: {
tag: {
name: 'island-project'
}
}
}
}
};
}

// Fetch all projects from all users with user info, hackatime links, and upvote data
// Server-side filtered and no tag data exposed to non-admins
const allProjects = await prisma.project.findMany({
where: whereClause,
select: {
projectID: true,
name: true,
Expand All @@ -29,6 +68,7 @@ export async function GET(request: Request) {
chat_enabled: true,
userId: true,
hackatimeLinks: true,
// No tag data exposed to anyone - server-side filtering only
user: {
select: {
name: true,
Expand Down
19 changes: 19 additions & 0 deletions app/api/island-project-types/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from 'next/server';
import { getIslandProjectTypesForClient } from '@/lib/islandProjectTypes';

/**
* API endpoint to get available island project types
* This allows the client to access environment variables securely
*/
export async function GET() {
try {
const projectTypes = getIslandProjectTypesForClient();
return NextResponse.json(projectTypes);
} catch (error) {
console.error('Error fetching island project types:', error);
return NextResponse.json(
{ error: 'Failed to fetch island project types' },
{ status: 500 }
);
}
}
69 changes: 63 additions & 6 deletions app/api/projects/[projectId]/chat/messages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ export async function GET(
select: {
chat_enabled: true,
userId: true, // Include userId to determine author
projectTags: {
select: {
tag: {
select: {
name: true
}
}
}
}
}
});

Expand All @@ -41,6 +50,9 @@ export async function GET(
return NextResponse.json({ error: 'Chat is not enabled for this project' }, { status: 403 });
}

// Check if this is an island project
const isIslandProject = project.projectTags.some(pt => pt.tag.name === 'island-project');

// Get the chat room for this project
const chatRoom = await prisma.chatRoom.findFirst({
where: {
Expand All @@ -66,24 +78,38 @@ export async function GET(
}

// Get messages from the chat room
const messages = await prisma.chatMessage.findMany({
const messageQueryOptions: any = {
where: whereClause,
orderBy: {
createdAt: sinceTimestamp ? 'asc' : 'desc', // If since timestamp, get oldest first; otherwise newest first
},
take: sinceTimestamp ? undefined : 100, // If since timestamp, get all new messages; otherwise limit to 100
});
};

// Only add include clause for island projects
if (isIslandProject) {
messageQueryOptions.include = {
user: {
select: {
name: true
}
}
};
}

const messages = await prisma.chatMessage.findMany(messageQueryOptions);

// If no since timestamp, reverse to get chronological order (oldest to newest) for display
const chronologicalMessages = sinceTimestamp ? messages : messages.reverse();

// Format messages for the client - include isAuthor flag
// Format messages for the client - include isAuthor flag and user name for island projects
const formattedMessages = chronologicalMessages.map(message => ({
id: message.id,
content: message.content,
userId: message.userId,
createdAt: message.createdAt.toISOString(),
isAuthor: message.userId === project.userId, // Flag to indicate if message is from project author
userName: isIslandProject && (message as any).user ? (message as any).user.name : undefined, // Include real name for island projects
}));

return NextResponse.json(formattedMessages);
Expand Down Expand Up @@ -135,6 +161,15 @@ export async function POST(
select: {
chat_enabled: true,
userId: true, // Include userId to determine author
projectTags: {
select: {
tag: {
select: {
name: true
}
}
}
}
}
});

Expand All @@ -146,6 +181,14 @@ export async function POST(
return NextResponse.json({ error: 'Chat is not enabled for this project' }, { status: 403 });
}

// Check if this is an island project
const isIslandProject = project.projectTags.some(pt => pt.tag.name === 'island-project');

// For island projects, only the project owner may post messages
if (isIslandProject && session.user.id !== project.userId) {
return NextResponse.json({ error: 'Only the project owner can write in island stories.' }, { status: 403 });
}

// Get or create the chat room for this project
let chatRoom = await prisma.chatRoom.findFirst({
where: {
Expand All @@ -164,13 +207,26 @@ export async function POST(
}

// Create the message
const message = await prisma.chatMessage.create({
const messageCreateOptions: any = {
data: {
content: body.content.trim(),
userId: session.user.id,
roomId: chatRoom.id,
},
});
}
};

// Only add include clause for island projects
if (isIslandProject) {
messageCreateOptions.include = {
user: {
select: {
name: true
}
}
};
}

const message = await prisma.chatMessage.create(messageCreateOptions);

// Format message for the client
const formattedMessage = {
Expand All @@ -179,6 +235,7 @@ export async function POST(
userId: message.userId,
createdAt: message.createdAt.toISOString(),
isAuthor: message.userId === project.userId, // Flag to indicate if message is from project author
userName: isIslandProject && (message as any).user ? (message as any).user.name : undefined, // Include real name for island projects
};

return NextResponse.json(formattedMessage);
Expand Down
Loading
Loading