- Backend completo com Express, TypeScript e Prisma - Sistema de autenticação JWT - API REST com todas as funcionalidades - Sistema de mensagens e chat em tempo real (Socket.io) - Upload e gerenciamento de fotos - Sistema de perfis com verificação - Busca avançada com filtros - Sistema de eventos - Dashboard administrativo - Frontend Next.js 14 com TypeScript - Design moderno com Tailwind CSS - Componentes UI com Radix UI - Tema dark/light - Configuração Nginx pronta para produção - Scripts de instalação e deploy - Documentação completa
358 lines
8.8 KiB
TypeScript
358 lines
8.8 KiB
TypeScript
import { Response } from 'express';
|
|
import { PrismaClient } from '@prisma/client';
|
|
import { AuthRequest } from '../middleware/auth.middleware';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
export const getEvents = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { page = 1, limit = 20, upcoming = 'true' } = req.query;
|
|
const skip = (Number(page) - 1) * Number(limit);
|
|
|
|
const where: any = { status: 'PUBLISHED' };
|
|
|
|
if (upcoming === 'true') {
|
|
where.date = { gte: new Date() };
|
|
}
|
|
|
|
const [events, total] = await Promise.all([
|
|
prisma.event.findMany({
|
|
where,
|
|
skip,
|
|
take: Number(limit),
|
|
include: {
|
|
creator: {
|
|
include: { profile: true }
|
|
},
|
|
participants: {
|
|
where: { status: 'approved' },
|
|
include: {
|
|
user: {
|
|
include: { profile: true }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
orderBy: { date: 'asc' }
|
|
}),
|
|
prisma.event.count({ where })
|
|
]);
|
|
|
|
res.json({
|
|
events,
|
|
pagination: {
|
|
page: Number(page),
|
|
limit: Number(limit),
|
|
total,
|
|
pages: Math.ceil(total / Number(limit))
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Erro ao buscar eventos:', error);
|
|
res.status(500).json({ error: 'Erro ao buscar eventos' });
|
|
}
|
|
};
|
|
|
|
export const getEvent = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const event = await prisma.event.findUnique({
|
|
where: { id },
|
|
include: {
|
|
creator: {
|
|
include: { profile: true }
|
|
},
|
|
participants: {
|
|
include: {
|
|
user: {
|
|
include: { profile: true }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ error: 'Evento não encontrado' });
|
|
}
|
|
|
|
// Verificar se é privado e se o usuário tem acesso
|
|
if (event.isPrivate && event.creatorId !== req.userId) {
|
|
const isParticipant = event.participants.some(
|
|
p => p.userId === req.userId && p.status === 'approved'
|
|
);
|
|
|
|
if (!isParticipant) {
|
|
return res.status(403).json({ error: 'Acesso negado' });
|
|
}
|
|
}
|
|
|
|
res.json(event);
|
|
} catch (error) {
|
|
console.error('Erro ao buscar evento:', error);
|
|
res.status(500).json({ error: 'Erro ao buscar evento' });
|
|
}
|
|
};
|
|
|
|
export const createEvent = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const {
|
|
title,
|
|
description,
|
|
date,
|
|
endDate,
|
|
location,
|
|
city,
|
|
state,
|
|
address,
|
|
maxParticipants,
|
|
isPrivate,
|
|
requiresApproval,
|
|
tags,
|
|
price
|
|
} = req.body;
|
|
|
|
let coverImage = null;
|
|
if (req.file) {
|
|
coverImage = `/uploads/photos/${req.file.filename}`;
|
|
}
|
|
|
|
const event = await prisma.event.create({
|
|
data: {
|
|
creatorId: req.userId!,
|
|
title,
|
|
description,
|
|
date: new Date(date),
|
|
endDate: endDate ? new Date(endDate) : null,
|
|
location,
|
|
city,
|
|
state,
|
|
address,
|
|
coverImage,
|
|
maxParticipants: maxParticipants ? Number(maxParticipants) : null,
|
|
isPrivate: isPrivate === 'true',
|
|
requiresApproval: requiresApproval !== 'false',
|
|
tags: tags ? JSON.parse(tags) : [],
|
|
price: price ? parseFloat(price) : null,
|
|
status: 'PUBLISHED'
|
|
},
|
|
include: {
|
|
creator: {
|
|
include: { profile: true }
|
|
}
|
|
}
|
|
});
|
|
|
|
res.status(201).json(event);
|
|
} catch (error) {
|
|
console.error('Erro ao criar evento:', error);
|
|
res.status(500).json({ error: 'Erro ao criar evento' });
|
|
}
|
|
};
|
|
|
|
export const updateEvent = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const event = await prisma.event.findUnique({
|
|
where: { id }
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ error: 'Evento não encontrado' });
|
|
}
|
|
|
|
if (event.creatorId !== req.userId) {
|
|
return res.status(403).json({ error: 'Acesso negado' });
|
|
}
|
|
|
|
const updateData: any = { ...req.body };
|
|
|
|
if (req.body.date) {
|
|
updateData.date = new Date(req.body.date);
|
|
}
|
|
|
|
if (req.body.endDate) {
|
|
updateData.endDate = new Date(req.body.endDate);
|
|
}
|
|
|
|
if (req.file) {
|
|
updateData.coverImage = `/uploads/photos/${req.file.filename}`;
|
|
}
|
|
|
|
if (req.body.tags) {
|
|
updateData.tags = JSON.parse(req.body.tags);
|
|
}
|
|
|
|
const updatedEvent = await prisma.event.update({
|
|
where: { id },
|
|
data: updateData,
|
|
include: {
|
|
creator: {
|
|
include: { profile: true }
|
|
},
|
|
participants: true
|
|
}
|
|
});
|
|
|
|
res.json(updatedEvent);
|
|
} catch (error) {
|
|
console.error('Erro ao atualizar evento:', error);
|
|
res.status(500).json({ error: 'Erro ao atualizar evento' });
|
|
}
|
|
};
|
|
|
|
export const deleteEvent = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const event = await prisma.event.findUnique({
|
|
where: { id }
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ error: 'Evento não encontrado' });
|
|
}
|
|
|
|
if (event.creatorId !== req.userId) {
|
|
return res.status(403).json({ error: 'Acesso negado' });
|
|
}
|
|
|
|
await prisma.event.delete({
|
|
where: { id }
|
|
});
|
|
|
|
res.json({ message: 'Evento deletado com sucesso' });
|
|
} catch (error) {
|
|
console.error('Erro ao deletar evento:', error);
|
|
res.status(500).json({ error: 'Erro ao deletar evento' });
|
|
}
|
|
};
|
|
|
|
export const joinEvent = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { message } = req.body;
|
|
|
|
const event = await prisma.event.findUnique({
|
|
where: { id },
|
|
include: {
|
|
participants: true
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ error: 'Evento não encontrado' });
|
|
}
|
|
|
|
// Verificar se já está participando
|
|
const existing = await prisma.eventParticipant.findUnique({
|
|
where: {
|
|
eventId_userId: {
|
|
eventId: id,
|
|
userId: req.userId!
|
|
}
|
|
}
|
|
});
|
|
|
|
if (existing) {
|
|
return res.status(400).json({ error: 'Você já está participando deste evento' });
|
|
}
|
|
|
|
// Verificar limite de participantes
|
|
if (event.maxParticipants) {
|
|
const approvedCount = event.participants.filter(p => p.status === 'approved').length;
|
|
if (approvedCount >= event.maxParticipants) {
|
|
return res.status(400).json({ error: 'Evento lotado' });
|
|
}
|
|
}
|
|
|
|
const status = event.requiresApproval ? 'pending' : 'approved';
|
|
|
|
const participant = await prisma.eventParticipant.create({
|
|
data: {
|
|
eventId: id,
|
|
userId: req.userId!,
|
|
status,
|
|
message
|
|
}
|
|
});
|
|
|
|
// Notificar criador
|
|
await prisma.notification.create({
|
|
data: {
|
|
userId: event.creatorId,
|
|
type: 'event',
|
|
title: 'Nova Solicitação de Participação',
|
|
message: `Alguém quer participar do seu evento: ${event.title}`,
|
|
link: `/events/${id}`
|
|
}
|
|
});
|
|
|
|
res.status(201).json(participant);
|
|
} catch (error) {
|
|
console.error('Erro ao participar do evento:', error);
|
|
res.status(500).json({ error: 'Erro ao participar do evento' });
|
|
}
|
|
};
|
|
|
|
export const leaveEvent = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
await prisma.eventParticipant.deleteMany({
|
|
where: {
|
|
eventId: id,
|
|
userId: req.userId
|
|
}
|
|
});
|
|
|
|
res.json({ message: 'Você saiu do evento' });
|
|
} catch (error) {
|
|
console.error('Erro ao sair do evento:', error);
|
|
res.status(500).json({ error: 'Erro ao sair do evento' });
|
|
}
|
|
};
|
|
|
|
export const updateParticipantStatus = async (req: AuthRequest, res: Response) => {
|
|
try {
|
|
const { id, participantId } = req.params;
|
|
const { status } = req.body;
|
|
|
|
const event = await prisma.event.findUnique({
|
|
where: { id }
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ error: 'Evento não encontrado' });
|
|
}
|
|
|
|
if (event.creatorId !== req.userId) {
|
|
return res.status(403).json({ error: 'Apenas o criador pode aprovar participantes' });
|
|
}
|
|
|
|
const participant = await prisma.eventParticipant.update({
|
|
where: { id: participantId },
|
|
data: { status }
|
|
});
|
|
|
|
// Notificar participante
|
|
await prisma.notification.create({
|
|
data: {
|
|
userId: participant.userId,
|
|
type: 'event',
|
|
title: status === 'approved' ? 'Participação Aprovada' : 'Participação Rejeitada',
|
|
message: `Sua participação no evento "${event.title}" foi ${status === 'approved' ? 'aprovada' : 'rejeitada'}`,
|
|
link: `/events/${id}`
|
|
}
|
|
});
|
|
|
|
res.json(participant);
|
|
} catch (error) {
|
|
console.error('Erro ao atualizar status do participante:', error);
|
|
res.status(500).json({ error: 'Erro ao atualizar status' });
|
|
}
|
|
};
|
|
|