@@ -10,7 +10,7 @@ const { sendTeamInviteEmail } = require("../utils/mailer");
1010const fetch = require ( "node-fetch" ) ;
1111const { get : cGet , set : cSet , del : cDel , keys : cKeys , TTL , invalidateEvent } = require ( "../utils/cache" ) ;
1212const { validateMoreDetails, normalizeRegistrationFields } = require ( "../utils/registration-fields" ) ;
13- const { pickEventUpdateData } = require ( "../utils/event-update" ) ;
13+ const { pickEventUpdateData, validateTeamSize } = require ( "../utils/event-update" ) ;
1414const {
1515 normalizeAssignedFacultyEmails,
1616 getCouncilOrganizerIdsForFaculty,
@@ -1060,6 +1060,10 @@ router.post(protected + "/create", authCheck, (req, res) => {
10601060 if ( dates && dates . length ) {
10611061 dates = dates . map ( ( d ) => new Date ( d ) ) ;
10621062 }
1063+ const teamCheck = validateTeamSize ( min_ppt , ma_ppt ) ;
1064+ if ( ! teamCheck . ok ) {
1065+ return res . status ( 400 ) . json ( { error : true , message : teamCheck . message } ) ;
1066+ }
10631067 const normalizedFields = normalizeRegistrationFields ( registration_fields ) ;
10641068 prisma . events
10651069 . create ( {
@@ -1181,6 +1185,19 @@ router.post(
11811185 ) ;
11821186 }
11831187
1188+ if ( field . ma_ppt !== undefined || field . min_ppt !== undefined ) {
1189+ const teamCheck = validateTeamSize (
1190+ field . min_ppt ?? existingEvent . min_ppt ,
1191+ field . ma_ppt ?? existingEvent . ma_ppt ,
1192+ ) ;
1193+ if ( ! teamCheck . ok ) {
1194+ return res . status ( 400 ) . json ( {
1195+ error : true ,
1196+ message : teamCheck . message ,
1197+ } ) ;
1198+ }
1199+ }
1200+
11841201 if ( req . user . role !== "COUNCIL" ) {
11851202 delete field . assigned_faculty_emails ;
11861203 } else if ( field . assigned_faculty_emails !== undefined ) {
@@ -1292,6 +1309,25 @@ router.post(
12921309 "Selected faculty must be configured in council settings." ,
12931310 } ) ;
12941311 }
1312+
1313+ const proposal = normalizeProposal (
1314+ existingEvent . proposal_document ,
1315+ ) ;
1316+ if ( ! proposal . document ) {
1317+ return res . status ( 400 ) . json ( {
1318+ error : true ,
1319+ message :
1320+ "Build and save a proposal document before submitting." ,
1321+ } ) ;
1322+ }
1323+ if ( ! allCouncilSignatoriesSigned ( proposal ) ) {
1324+ return res . status ( 400 ) . json ( {
1325+ error : true ,
1326+ message :
1327+ "Every council signatory on the proposal must sign before submitting." ,
1328+ } ) ;
1329+ }
1330+
12951331 field . assigned_faculty_emails = assigned ;
12961332 field . comment = null ;
12971333 }
@@ -2695,6 +2731,139 @@ router.get(protected + "/export-participants/:id", authCheck, async (req, res) =
26952731 }
26962732} ) ;
26972733
2734+ /**
2735+ * GET /p/delete-preview/:id
2736+ * Counts of data that will be removed if the event is deleted.
2737+ */
2738+ router . get ( protected + "/delete-preview/:id" , authCheck , async ( req , res ) => {
2739+ if ( ! req . user ) return res . status ( 401 ) . json ( { error : true , message : "Unauthorized" } ) ;
2740+ const eventId = parseInt ( req . params . id ) ;
2741+ if ( isNaN ( eventId ) ) return res . status ( 400 ) . json ( { error : true , message : "Invalid id" } ) ;
2742+
2743+ const denied = await assertCouncilEventAccess ( req . user , eventId ) ;
2744+ if ( denied ) return res . status ( denied . status ) . json ( { error : true , message : denied . message } ) ;
2745+
2746+ try {
2747+ const event = await prisma . events . findUnique ( {
2748+ where : { id : eventId } ,
2749+ select : { name : true } ,
2750+ } ) ;
2751+ if ( ! event ) {
2752+ return res . status ( 404 ) . json ( { error : true , message : "Event not found" } ) ;
2753+ }
2754+
2755+ const [
2756+ participants ,
2757+ teams ,
2758+ attended ,
2759+ documents ,
2760+ budgetItems ,
2761+ announcements ,
2762+ childEvents ,
2763+ ] = await Promise . all ( [
2764+ prisma . participant . count ( { where : { event_id : eventId } } ) ,
2765+ prisma . team . count ( { where : { event_id : eventId } } ) ,
2766+ prisma . participant . count ( {
2767+ where : { event_id : eventId , attended : true } ,
2768+ } ) ,
2769+ prisma . eventDocument . count ( { where : { event_id : eventId } } ) ,
2770+ prisma . budgetItem . count ( { where : { event_id : eventId } } ) ,
2771+ prisma . announcement . count ( { where : { event_id : eventId } } ) ,
2772+ prisma . events . count ( { where : { parent_id : eventId } } ) ,
2773+ ] ) ;
2774+
2775+ return res . json ( {
2776+ error : false ,
2777+ preview : {
2778+ name : event . name ,
2779+ participants,
2780+ teams,
2781+ attended,
2782+ documents,
2783+ budgetItems,
2784+ announcements,
2785+ childEvents,
2786+ } ,
2787+ } ) ;
2788+ } catch ( err ) {
2789+ logger . error ( err ) ;
2790+ return res . status ( 500 ) . json ( {
2791+ error : true ,
2792+ message : "Error loading delete preview" ,
2793+ } ) ;
2794+ }
2795+ } ) ;
2796+
2797+ async function deleteEventTree ( eventId , deletedIds = [ ] ) {
2798+ const children = await prisma . events . findMany ( {
2799+ where : { parent_id : eventId } ,
2800+ select : { id : true } ,
2801+ } ) ;
2802+ for ( const child of children ) {
2803+ await deleteEventTree ( child . id , deletedIds ) ;
2804+ }
2805+ await prisma . events . delete ( { where : { id : eventId } } ) ;
2806+ deletedIds . push ( eventId ) ;
2807+ return deletedIds ;
2808+ }
2809+
2810+ /**
2811+ * POST /p/delete/:id
2812+ * Permanently delete an event and all related data (participants, teams, etc.).
2813+ * Body: { confirm_name: string } must match the event name exactly.
2814+ */
2815+ router . post ( protected + "/delete/:id" , authCheck , async ( req , res ) => {
2816+ if ( ! req . user ) return res . status ( 401 ) . json ( { error : true , message : "Unauthorized" } ) ;
2817+ if ( req . user . role !== "COUNCIL" ) {
2818+ return res . status ( 403 ) . json ( { error : true , message : "Forbidden" } ) ;
2819+ }
2820+
2821+ const eventId = parseInt ( req . params . id ) ;
2822+ if ( isNaN ( eventId ) ) return res . status ( 400 ) . json ( { error : true , message : "Invalid id" } ) ;
2823+
2824+ const denied = await assertCouncilEventAccess ( req . user , eventId ) ;
2825+ if ( denied ) return res . status ( denied . status ) . json ( { error : true , message : denied . message } ) ;
2826+
2827+ try {
2828+ const event = await prisma . events . findUnique ( {
2829+ where : { id : eventId } ,
2830+ select : { name : true } ,
2831+ } ) ;
2832+ if ( ! event ) {
2833+ return res . status ( 404 ) . json ( { error : true , message : "Event not found" } ) ;
2834+ }
2835+
2836+ const confirmName =
2837+ req . body ?. confirm_name != null
2838+ ? String ( req . body . confirm_name ) . trim ( )
2839+ : "" ;
2840+ if ( confirmName !== event . name ) {
2841+ return res . status ( 400 ) . json ( {
2842+ error : true ,
2843+ message :
2844+ "Type the exact event name to confirm permanent deletion." ,
2845+ } ) ;
2846+ }
2847+
2848+ const deletedIds = await deleteEventTree ( eventId ) ;
2849+ for ( const id of deletedIds ) {
2850+ invalidateEvent ( id , req . user . id ) ;
2851+ }
2852+
2853+ return res . json ( {
2854+ error : false ,
2855+ message : "Event deleted permanently." ,
2856+ deleted_count : deletedIds . length ,
2857+ } ) ;
2858+ } catch ( err ) {
2859+ logger . error ( err ) ;
2860+ return res . status ( 500 ) . json ( {
2861+ error : true ,
2862+ message : "Error deleting event" ,
2863+ } ) ;
2864+ }
2865+ } ) ;
2866+
26982867router . get (
26992868 protected + "/attendance-report/:id" ,
27002869 authCheck ,
0 commit comments