mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:07:20 +00:00
- ModuleManager: Central tracking for installed marketplace modules - DataAnalyzerWidget: Real-time CPU/RAM/Battery/Storage widget (unlocked by Data Analyzer module) - BottomNavBar: Navigation bar for Projects/Chat/Marketplace/Settings - RootShell: Real root command execution utility - TerminalActivity: Full root shell with neofetch, sysinfo, real Linux commands - Terminal Pro module: Adds aliases (ll, la, h), command history - ArcadeActivity + SnakeGame: Pixel Arcade module unlocks retro games - fade_in/fade_out animations for smooth transitions
474 lines
12 KiB
TypeScript
474 lines
12 KiB
TypeScript
/**
|
|
* Community Routes
|
|
* API endpoints for events, opportunities, and community posts
|
|
* Uses Supabase for data storage
|
|
*/
|
|
|
|
import express, { Request, Response } from 'express';
|
|
import { supabase } from './supabase.js';
|
|
|
|
const router = express.Router();
|
|
|
|
// Auth middleware helper
|
|
function getUserId(req: Request): string | null {
|
|
return (req.session as any)?.userId || null;
|
|
}
|
|
|
|
// ==================== EVENTS ROUTES ====================
|
|
|
|
// GET /api/events - List all events
|
|
router.get('/events', async (req: Request, res: Response) => {
|
|
try {
|
|
const { category, featured, upcoming, limit = 20, offset = 0 } = req.query;
|
|
|
|
let query = supabase
|
|
.from('community_events')
|
|
.select('*', { count: 'exact' });
|
|
|
|
if (category && category !== 'all') {
|
|
query = query.eq('category', category as string);
|
|
}
|
|
|
|
if (featured === 'true') {
|
|
query = query.eq('featured', true);
|
|
}
|
|
|
|
if (upcoming === 'true') {
|
|
query = query.gte('date', new Date().toISOString());
|
|
}
|
|
|
|
const { data, error, count } = await query
|
|
.order('date', { ascending: true })
|
|
.range(Number(offset), Number(offset) + Number(limit) - 1);
|
|
|
|
if (error) throw error;
|
|
|
|
res.json({
|
|
events: data || [],
|
|
total: count || 0
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[Events] List error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/events/:id - Get single event
|
|
router.get('/events/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const { data, error } = await supabase
|
|
.from('community_events')
|
|
.select('*')
|
|
.eq('id', id)
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
if (!data) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
|
|
res.json(data);
|
|
} catch (error: any) {
|
|
console.error('[Events] Get error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/events/:id/register - Register for event
|
|
router.post('/events/:id/register', async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const userId = getUserId(req);
|
|
|
|
if (!userId) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
// Check event exists and not full
|
|
const { data: event, error: eventError } = await supabase
|
|
.from('community_events')
|
|
.select('id, capacity, attendees')
|
|
.eq('id', id)
|
|
.single();
|
|
|
|
if (eventError || !event) {
|
|
return res.status(404).json({ error: 'Event not found' });
|
|
}
|
|
|
|
if (event.capacity && event.attendees >= event.capacity) {
|
|
return res.status(400).json({ error: 'Event is full' });
|
|
}
|
|
|
|
// Register user (trigger will update attendees count)
|
|
const { error: regError } = await supabase
|
|
.from('community_event_registrations')
|
|
.insert({
|
|
event_id: id,
|
|
user_id: userId
|
|
});
|
|
|
|
if (regError) {
|
|
if (regError.code === '23505') {
|
|
return res.status(400).json({ error: 'Already registered for this event' });
|
|
}
|
|
throw regError;
|
|
}
|
|
|
|
// Get updated event
|
|
const { data: updatedEvent } = await supabase
|
|
.from('community_events')
|
|
.select('*')
|
|
.eq('id', id)
|
|
.single();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Successfully registered for event',
|
|
event: updatedEvent
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[Events] Register error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/events - Create new event
|
|
router.post('/events', async (req: Request, res: Response) => {
|
|
try {
|
|
const userId = getUserId(req);
|
|
if (!userId) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
const { title, description, category, date, end_date, location, is_virtual, price, capacity } = req.body;
|
|
|
|
if (!title || !category || !date) {
|
|
return res.status(400).json({ error: 'title, category, and date are required' });
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from('community_events')
|
|
.insert({
|
|
title,
|
|
description,
|
|
category,
|
|
date,
|
|
end_date,
|
|
location,
|
|
is_virtual: is_virtual || false,
|
|
price: price || 0,
|
|
capacity,
|
|
organizer_id: userId
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
res.status(201).json(data);
|
|
} catch (error: any) {
|
|
console.error('[Events] Create error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ==================== OPPORTUNITIES ROUTES ====================
|
|
|
|
// GET /api/opportunities - List all job opportunities
|
|
router.get('/opportunities', async (req: Request, res: Response) => {
|
|
try {
|
|
const { arm, type, remote, limit = 20, offset = 0 } = req.query;
|
|
|
|
let query = supabase
|
|
.from('community_opportunities')
|
|
.select('*', { count: 'exact' })
|
|
.eq('is_active', true);
|
|
|
|
if (arm && arm !== 'all') {
|
|
query = query.eq('arm', arm as string);
|
|
}
|
|
|
|
if (type && type !== 'all') {
|
|
query = query.eq('type', type as string);
|
|
}
|
|
|
|
if (remote === 'true') {
|
|
query = query.eq('is_remote', true);
|
|
}
|
|
|
|
const { data, error, count } = await query
|
|
.order('created_at', { ascending: false })
|
|
.range(Number(offset), Number(offset) + Number(limit) - 1);
|
|
|
|
if (error) throw error;
|
|
|
|
res.json({
|
|
opportunities: data || [],
|
|
total: count || 0
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[Opportunities] List error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/opportunities/:id - Get single opportunity
|
|
router.get('/opportunities/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const { data, error } = await supabase
|
|
.from('community_opportunities')
|
|
.select('*')
|
|
.eq('id', id)
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
if (!data) {
|
|
return res.status(404).json({ error: 'Opportunity not found' });
|
|
}
|
|
|
|
res.json(data);
|
|
} catch (error: any) {
|
|
console.error('[Opportunities] Get error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/opportunities/:id/apply - Apply to job
|
|
router.post('/opportunities/:id/apply', async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const userId = getUserId(req);
|
|
|
|
if (!userId) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
const { resume_url, cover_letter } = req.body;
|
|
|
|
// Check opportunity exists
|
|
const { data: opportunity, error: oppError } = await supabase
|
|
.from('community_opportunities')
|
|
.select('id, is_active')
|
|
.eq('id', id)
|
|
.single();
|
|
|
|
if (oppError || !opportunity) {
|
|
return res.status(404).json({ error: 'Opportunity not found' });
|
|
}
|
|
|
|
if (!opportunity.is_active) {
|
|
return res.status(400).json({ error: 'This opportunity is no longer accepting applications' });
|
|
}
|
|
|
|
// Submit application
|
|
const { error: appError } = await supabase
|
|
.from('community_applications')
|
|
.insert({
|
|
opportunity_id: id,
|
|
user_id: userId,
|
|
resume_url,
|
|
cover_letter
|
|
});
|
|
|
|
if (appError) {
|
|
if (appError.code === '23505') {
|
|
return res.status(400).json({ error: 'Already applied to this opportunity' });
|
|
}
|
|
throw appError;
|
|
}
|
|
|
|
// Get updated opportunity
|
|
const { data: updatedOpp } = await supabase
|
|
.from('community_opportunities')
|
|
.select('*')
|
|
.eq('id', id)
|
|
.single();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Application submitted successfully',
|
|
opportunity: updatedOpp
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[Opportunities] Apply error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/opportunities - Create new opportunity
|
|
router.post('/opportunities', async (req: Request, res: Response) => {
|
|
try {
|
|
const userId = getUserId(req);
|
|
if (!userId) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
const { title, company, arm, type, location, is_remote, description, requirements, salary_min, salary_max } = req.body;
|
|
|
|
if (!title || !company || !arm || !type) {
|
|
return res.status(400).json({ error: 'title, company, arm, and type are required' });
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from('community_opportunities')
|
|
.insert({
|
|
title,
|
|
company,
|
|
arm,
|
|
type,
|
|
location,
|
|
is_remote: is_remote || false,
|
|
description,
|
|
requirements: requirements || [],
|
|
salary_min,
|
|
salary_max,
|
|
posted_by: userId
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
res.status(201).json(data);
|
|
} catch (error: any) {
|
|
console.error('[Opportunities] Create error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ==================== COMMUNITY POSTS ROUTES ====================
|
|
|
|
// GET /api/posts - List community posts
|
|
router.get('/posts', async (req: Request, res: Response) => {
|
|
try {
|
|
const { arm, limit = 20, offset = 0 } = req.query;
|
|
|
|
let query = supabase
|
|
.from('community_posts')
|
|
.select('*', { count: 'exact' })
|
|
.eq('is_published', true);
|
|
|
|
if (arm && arm !== 'all') {
|
|
query = query.eq('arm_affiliation', arm as string);
|
|
}
|
|
|
|
const { data, error, count } = await query
|
|
.order('created_at', { ascending: false })
|
|
.range(Number(offset), Number(offset) + Number(limit) - 1);
|
|
|
|
if (error) throw error;
|
|
|
|
res.json({
|
|
posts: data || [],
|
|
total: count || 0
|
|
});
|
|
} catch (error: any) {
|
|
console.error('[Posts] List error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/posts - Create new post
|
|
router.post('/posts', async (req: Request, res: Response) => {
|
|
try {
|
|
const userId = getUserId(req);
|
|
if (!userId) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
const { title, content, arm_affiliation, tags, category } = req.body;
|
|
|
|
if (!title || !content) {
|
|
return res.status(400).json({ error: 'title and content are required' });
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from('community_posts')
|
|
.insert({
|
|
title,
|
|
content,
|
|
arm_affiliation: arm_affiliation || 'general',
|
|
tags: tags || [],
|
|
category,
|
|
author_id: userId
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
res.status(201).json(data);
|
|
} catch (error: any) {
|
|
console.error('[Posts] Create error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/posts/:id/like - Like a post
|
|
router.post('/posts/:id/like', async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const userId = getUserId(req);
|
|
|
|
if (!userId) {
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
}
|
|
|
|
// Toggle like
|
|
const { data: existingLike } = await supabase
|
|
.from('community_post_likes')
|
|
.select('id')
|
|
.eq('post_id', id)
|
|
.eq('user_id', userId)
|
|
.single();
|
|
|
|
if (existingLike) {
|
|
// Unlike
|
|
await supabase
|
|
.from('community_post_likes')
|
|
.delete()
|
|
.eq('id', existingLike.id);
|
|
|
|
// Decrement likes count
|
|
await supabase
|
|
.from('community_posts')
|
|
.update({ likes_count: supabase.rpc('decrement', { x: 1 }) })
|
|
.eq('id', id);
|
|
|
|
res.json({ success: true, liked: false });
|
|
} else {
|
|
// Like
|
|
await supabase
|
|
.from('community_post_likes')
|
|
.insert({ post_id: id, user_id: userId });
|
|
|
|
// Increment likes count
|
|
await supabase
|
|
.from('community_posts')
|
|
.update({ likes_count: supabase.rpc('increment', { x: 1 }) })
|
|
.eq('id', id);
|
|
|
|
res.json({ success: true, liked: true });
|
|
}
|
|
} catch (error: any) {
|
|
console.error('[Posts] Like error:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// ==================== LEGACY MESSAGES ROUTES ====================
|
|
// Note: Real messaging is handled by messaging-routes.ts
|
|
// These routes provide backward compatibility
|
|
|
|
router.get('/messages', async (req: Request, res: Response) => {
|
|
res.json({
|
|
messages: [],
|
|
note: 'Use /api/conversations for messaging'
|
|
});
|
|
});
|
|
|
|
export default router;
|