Updated changes in backend

This commit is contained in:
2026-06-19 11:54:20 +05:30
parent 7fb507c47e
commit ee81ca9228
33 changed files with 40 additions and 2538 deletions

View File

@@ -24,7 +24,7 @@ The EMS platform addresses these challenges by offering:
## 2. System Architecture & Tech Stack ## 2. System Architecture & Tech Stack
The platform is designed around a modern decoupled client-server architecture. The platform runs on a serverless, database-first architecture using Firebase. All backend endpoints are intercepted and resolved client-side in the browser.
```mermaid ```mermaid
graph LR graph LR
@@ -33,31 +33,24 @@ graph LR
Router[Component & Context Routing] Router[Component & Context Routing]
Tailwind[Tailwind CSS & Glassmorphism UI] Tailwind[Tailwind CSS & Glassmorphism UI]
Framer[Framer Motion Animations] Framer[Framer Motion Animations]
Interceptor[Fetch Interceptor firebaseBackend.ts]
end end
subgraph Backend [Spring Boot API Server] subgraph Backend [Firebase Cloud Platform]
Controller[REST API Controllers] Firestore[(Cloud Firestore Database)]
Security[Spring Security Config] Auth[Firebase Authentication]
Services[JPA / Hibernate Data Access]
end end
subgraph Database [Relational Storage] React <--> Interceptor
MySQL[(Local MySQL ems_db)] Interceptor <-->|Firebase Client SDK| Firestore
end Interceptor <-->|Firebase Client SDK| Auth
React <-->|REST over HTTP| Controller
Controller <--> Services
Services <--> MySQL
``` ```
### Backend Specifications ### Firebase Backend Specifications
* **Core Framework:** Spring Boot 3.2.5 (Java 17) * **Database Service:** Cloud Firestore for real-time document storage.
* **ORM & Data Access:** Spring Data JPA with Hibernate * **Data Access & Interception:** A client-side fetch interceptor implemented in `firebaseBackend.ts` intercepts all `/api/*` REST HTTP requests and handles the queries/transactions natively using the Firebase Client SDK.
* **Database Driver:** MySQL Connector/J (`com.mysql.cj.jdbc.Driver`) * **Authentication:** Google OAuth 2.0 and email/passcode flows, integrated with Firebase Auth and sync'd to the `ems_users` Firestore collection.
* **Security:** Spring Security (Permit-all filter bypass, with role-based checks and authorization handling verified inside the controller/service scope). * **Security & Roles:** Verified inside frontend routing handlers and secured using Firestore Security Rules.
* **Cryptographic Hashing:** `BCryptPasswordEncoder` for storing/verifying user passwords.
* **Dev Tools:** Project Lombok, Spring Boot DevTools.
* **Port Configuration:** Runs on port `8081` (CORS-enabled for frontend origins).
### Frontend Specifications ### Frontend Specifications
* **Core Framework:** React 19, TypeScript, Vite * **Core Framework:** React 19, TypeScript, Vite
@@ -73,7 +66,7 @@ graph LR
## 3. Database Schema & Architecture ## 3. Database Schema & Architecture
The database contains four primary entities managed via Spring Data JPA. The database contains four primary collections managed in Cloud Firestore.
```mermaid ```mermaid
erDiagram erDiagram
@@ -338,16 +331,16 @@ graph TD
## 6. Key Workflows & Logic Specs ## 6. Key Workflows & Logic Specs
### A. Real-time Conflict Auditing Engine ### A. Real-time Conflict Auditing Engine
During event creation, the backend validates for timing and location conflicts. During event creation, the application validates for timing and location conflicts directly against Firestore.
1. The backend runs the custom JPA method `findConflictingEvents()` matching location, institution, status (`APPROVED`), and overlapping times: 1. The frontend query helper scan the `ems_events` collection to identify overlapping bookings:
$$\text{Start}_A < \text{End}_B \quad \text{and} \quad \text{End}_A > \text{Start}_B$$ $$\text{Start}_A < \text{End}_B \quad \text{and} \quad \text{End}_A > \text{Start}_B$$
2. **Placement Override Case:** 2. **Placement Override Case:**
If the proposer is a `PLACEMENT` coordinator and `cancelConflicting=true` is checked: If the proposer is a `PLACEMENT` coordinator and `cancelConflicting=true` is checked:
* The backend sets all conflicting events to `CANCELLED`. * The fetch interceptor updates all conflicting Firestore event documents' status to `CANCELLED`.
* The backend registers a rejection/displacement reason on the cancelled events. * It registers a rejection/displacement reason on the cancelled events.
* The placement event is saved in `PENDING_PR` (for Principal review). * The placement event is saved in `PENDING_PR` (for Principal review).
3. **General Case:** 3. **General Case:**
If conflicts exist and it is not an overridden placement, the backend throws a `409 Conflict` HTTP exception with conflicting details. If conflicts exist and it is not an overridden placement, a conflict message is returned and flagged to the user.
### B. Event Approval Pipeline ### B. Event Approval Pipeline
The approval flow routes events dynamically depending on the proposer: The approval flow routes events dynamically depending on the proposer:
@@ -387,27 +380,9 @@ This tool allows admins/HODs/Principals to duplicate schedules for new semesters
## 7. Developer & Local Deployment Guide ## 7. Developer & Local Deployment Guide
### Prerequisites ### Prerequisites
* Java JDK 17
* Node.js (v18+) * Node.js (v18+)
* MySQL Database Server
### 1. Database Setup ### 1. Launch Frontend Client
1. Start your local MySQL instance.
2. Create the target schema (automatic if configuration is active):
```sql
CREATE DATABASE ems_db;
```
3. Update the credentials in `backend/src/main/resources/application.properties` (defaults: username `root`, password `Abiram@07`).
### 2. Launch Backend Server
1. Navigate to the `backend` folder.
2. Compile and run using Maven:
```bash
mvnw spring-boot:run
```
3. The server starts at `http://localhost:8081`. The `DataInitializer` runs on startup to seed the default user accounts.
### 3. Launch Frontend Client
1. Navigate to the `frontend` folder. 1. Navigate to the `frontend` folder.
2. Install dependencies: 2. Install dependencies:
```bash ```bash
@@ -419,7 +394,11 @@ This tool allows admins/HODs/Principals to duplicate schedules for new semesters
``` ```
4. The frontend runs at `http://localhost:5173`. 4. The frontend runs at `http://localhost:5173`.
### 4. Seed User Accounts & Logins ### 2. Firebase Configurations
* The Firebase client config is hardcoded in `frontend/src/lib/firebaseBackend.ts` to connect directly to the Firestore backend.
* On first load, if the database collections (`ems_users` or `ems_classes`) are empty, `firebaseBackend.ts` automatically seeds default institutional accounts and mock classes.
### 3. Seed User Accounts & Logins
On startup, the system seeds accounts with their default passwords: On startup, the system seeds accounts with their default passwords:
* **Faculty Member:** `faculty@rit.edu` / `faculty123` * **Faculty Member:** `faculty@rit.edu` / `faculty123`
* **HOD:** `hod@rit.edu` / `hod123` * **HOD:** `hod@rit.edu` / `hod123`

View File

@@ -1,358 +0,0 @@
# Rajalakshmi Institute of Technology (RIT) Events Hub — System Documentation
Welcome to the comprehensive system documentation for the **RIT Events Hub**, a premium, high-fidelity academic event management, tracking, and auditing portal designed for Rajalakshmi Institute of Technology.
This document provides a complete breakdown of the website's architecture, user roles, file structures, database schema, operational workflows, and features, accompanied by a detailed site map.
---
## 1. Overview & Core Mission
The **RIT Events Hub** is an institutional web application designed to digitize and manage the entire lifecycle of college events. It replaces manual event scheduling, paper registrations, physical ticket checks, and fragmented certificate auditing with a unified, secure portal.
### Key Objectives:
* **Decentralized Event Creation:** Enable faculty members to coordinate, schedule, and estimate budgets for departmental activities.
* **Hierarchical Approvals:** Ensure events undergo appropriate institutional oversight through a HOD-to-Admin approval pipeline.
* **Intelligent Resource Management:** Prevent venue conflicts and respect departmental/sectional seat quotas.
* **Continuous Verification for Students:** Link student attendance, certificate uploads, and On-Duty (OD) approvals step-by-step.
* **Security & Accessibility:** Provide separate, secure authentication models for internal students, external students, coordinators (faculty/HOD), and administrators.
---
## 2. Technology Stack & Integrations
The platform is built using a modern, fast, and secure web development stack:
| Layer | Technology | Description |
| :--- | :--- | :--- |
| **Frontend Framework** | React (v19), TypeScript, Vite | Multi-page single-page application (SPA) state-managed structure. |
| **Styling & Icons** | Tailwind CSS (CDN), FontAwesome | Curated responsive styling using professional color palettes (Deep Blue `#004a99` and Academic Orange `#f97316`). |
| **Backend & Auth** | Supabase (PostgreSQL, Storage, Auth) | Manages authentication, row-level security (RLS), document uploads, and database queries. |
| **PDF Generation** | `jspdf` & `jspdf-autotable` | Compiles detailed, printable event proposals containing budget sheets, schedules, and resource details. |
| **Layout Tools** | `html2canvas` | Used to capture ticket layouts for student downloads. |
| **Fonts** | Google Fonts (Inter, Playfair Display) | Custom typography reflecting academic elegance. |
---
## 3. Database Schema
The database relies on a PostgreSQL schema managed via Supabase. Below are the primary tables and relations:
### `Studentusers` (Internal Students)
* `id` (UUID, Primary Key, references auth.users)
* `name` (Text)
* `email` (Text, Unique)
* `reg_no` (Text, Unique)
* `phone` (Text)
* `department` (Text)
* `year` (Text)
* `section` (Text)
* `college_name` (Text - Default: 'RAJALAKSHMI INSTITUTE OF TECHNOLOGY')
* `updated_at` (Timestamp)
### `externalusers` (External Students)
* `id` (UUID, Primary Key, references auth.users)
* `name` (Text)
* `email` (Text, Unique)
* `reg_no` (Text)
* `phone` (Text)
* `department` (Text)
* `year` (Text)
* `section` (Text)
* `college` (Text)
* `college_location` (Text)
* `gender` (Text)
* `updated_at` (Timestamp)
### `Facultyusers` (Coordinators & HODs)
* `id` (UUID, Primary Key, references auth.users)
* `name` (Text)
* `email` (Text, Unique)
* `dept` (Text)
* `role` (Text - e.g., 'Faculty', 'HOD', 'System Admin')
* `phone` (Text)
* `profile_photo` (Text - URL)
* `updated_at` (Timestamp)
### `Adminusers` (Principal / Director / Administrators)
* `id` (UUID, Primary Key, references auth.users)
* `name` (Text)
* `email` (Text, Unique)
* `dept` (Text - 'Administration')
* `updated_at` (Timestamp)
### `events`
* `id` (UUID, Primary Key)
* `title` (Text)
* `location` (Text - Venue name)
* `date` (Text - Standard format)
* `category` (Text - 'TECHNICAL' \| 'NON-TECHNICAL' \| 'WORKSHOP' \| 'CENTRE-ACTIVITY')
* `domain` (Text - references domains.name)
* `pricing_type` (Text - 'FREE' \| 'PAID')
* `coordinator` (Text - Coordinator Name)
* `club` (Text)
* `image` (Text - Supabase storage URL)
* `status` (Text - 'Scheduled' \| 'Event Ongoing' \| 'Completed')
* `max_participants` (Integer)
* `registration_deadline` (Timestamp)
* `duration_days` (Integer)
* `event_summary` (Text)
* `is_team_event` (Boolean)
* `team_size_limit` (Integer)
* `team_composition` (Text - 'MIXED' \| 'INTER_DEPT')
* `participant_type` (Text - 'INTERNAL' \| 'EXTERNAL' \| 'BOTH')
* `verification_status` (Text - 'PENDING' \| 'PENDING_HOD' \| 'PENDING_ADMIN' \| 'APPROVED' \| 'REJECTED')
* `refreshment_expense` (Numeric)
* `transportation_expense` (Numeric)
* `session_coverage_fee` (Numeric)
* `total_expense` (Numeric)
* `conducting_dept` (Text)
* `created_by` (UUID, references auth.users)
* `request_by_faculty` (Timestamp)
* `request_by_hod` (Timestamp)
### `event_schedule` (Session/Batch Slots)
* `id` (UUID, Primary Key)
* `event_id` (UUID, references events.id)
* `day_idx` (Integer)
* `batch_idx` (Integer)
* `date` (Text)
* `start_time` (Text)
* `end_time` (Text)
### `resource_persons` (Guest Details)
* `id` (UUID, Primary Key)
* `event_id` (UUID, references events.id)
* `day_idx` (Integer)
* `batch_idx` (Integer)
* `schedule_id` (UUID, references event_schedule.id)
* `type` (Text - 'INTERNAL' \| 'EXTERNAL')
* `name` (Text)
* `dept` (Text)
* `college_name` (Text)
* `phone` (Text)
* `email` (Text)
### `event_dept_limits` (Departmental Seat Allocations)
* `id` (UUID, Primary Key)
* `event_id` (UUID, references events.id)
* `department` (Text)
* `max_seats` (Integer)
* `section_limits` (JSONB - Maps sections (A, B, C...) to integer limits)
### `registrations` (Student Bookings)
* `id` (Text, Primary Key - Structured as `${userId}_${eventId}`)
* `user_id` (UUID, references auth.users)
* `event_id` (UUID, references events.id)
* `user_email` (Text)
* `user_name` (Text)
* `reg_no` (Text)
* `phone` (Text)
* `gender` (Text)
* `dept` (Text)
* `section` (Text)
* `year` (Text)
* `college` (Text)
* `payment_status` (Text - 'PENDING' \| 'COMPLETED')
* `registered_at` (Timestamp)
* `team_code` (Text)
* `team_name` (Text)
* `is_team_leader` (Boolean)
* `certification_url` (Text)
* `certification_status` (Text - 'PENDING' \| 'APPROVED' \| 'REJECTED')
* `od_url` (Text)
* `od` (Boolean - OD approval)
### `announcements` (Notice Board)
* `id` (UUID, Primary Key)
* `title` (Text)
* `message` (Text)
* `type` (Text - 'DELAY' \| 'INFO' \| 'URGENT' \| 'ENDED' \| 'ONGOING')
* `event_id` (UUID, references events.id)
* `timestamp` (Timestamp)
* `expires_at` (Timestamp)
### `special_events` (Symposiums & External Links)
* `id` (UUID, Primary Key)
* `title` (Text)
* `description` (Text)
* `link` (Text)
* `created_by` (UUID)
* `is_active` (Boolean)
* `verification_status` (Text - 'PENDING' \| 'APPROVED' \| 'REJECTED')
### `domains`
* `id` (UUID, Primary Key)
* `name` (Text, Unique)
* `category` (Text)
* `image` (Text)
* `status` (Text - 'PENDING' \| 'APPROVED' \| 'REJECTED')
* `description` (Text)
---
## 4. Operational Workflows & Key Logic
### A. Core Blockage (Preventing Registration Overhead)
To maintain academic integrity, a student **cannot register for any new events** if they have an unfinalized event from the past.
* **Condition:** If a student is registered for an event that has completed (`status === 'Completed'`), they must upload their certificate and obtain coordinator approval (`certification_status === 'APPROVED'`) and OD status before the system allows them to click "Register" on any future catalog events.
### B. Overlap Prevention (Intelligent Venue Booking)
During event creation, coordinates are validated against active bookings:
* **Conflict Condition:** When a coordinator selects a venue and date, the system queries existing approved/pending events. If another event occupies the same location, and the date ranges overlap based on the `durationDays`, that venue will be flagged as "Booked" or "Under Verification" in the dropdown, disabling selection.
### C. Quota-Constrained Bookings (Sectional & Departmental Caps)
The event organizer can set constraints for RIT students:
1. **Department Limits:** Limits registration to `X` seats per department.
2. **Section Limits:** Further restricts seats down to individual sections (e.g., CSE section `A` gets 10 seats, section `B` gets 5 seats).
3. **Validation:** When a student attempts registration, the system evaluates their profile (department, section) and checks the respective quota counts in the `registrations` table. If the quota is full, booking is blocked.
### D. Team play Alliance (Team Registrations)
* For team events, the first student registers and clicks "Form Team", which inserts a random 6-character alphanumeric code (`team_code`) and designates them as `is_team_leader`.
* Subsequent students register for the event, click "Join Team", and input the code. The system verifies the team is not full (respecting `teamSizeLimit`) and matches `INTER_DEPT` composition requirements before assigning the `team_code` to their registration.
### E. Proposal PDF Compile
Coordinators and Admin can generate a standardized PDF proposal containing:
1. General overview (departments, clubs, metadata).
2. Logistical details (location, dates, capacity, formats).
3. Day-by-day itineraries mapped to guest resource persons and timings.
4. Departmental seat allocation tables.
5. Financial projections (Refreshment + Transportation + Session Fees = Total projected budget).
6. Signature/Verification box showing HOD & Admin approval timestamps.
---
## 5. Portal Flow & Visual Site Map
### A. Navigational Site Map Structure
```
[WELCOME GATEWAY]
├── STUDENT PORTAL ────► [STUDENT AUTH (Login / Sign Up)]
│ │
│ ├── HOME DASHBOARD (Notices, Banner, Stats)
│ ├── EVENTS CATALOG (Details, Booking, Team Play)
│ ├── REGISTRATIONS (QR Tickets, Track Progress)
│ ├── STATUS TRACKER (Detailed Lifecycle Step Visualizer)
│ └── PROFILE VIEW (Update Info, Uploaded Credentials)
├── FACULTY PORTAL ────► [COORDINATOR AUTH (Faculty / HOD)]
│ │
│ ├── OVERVIEW (Metrics & Quick Gateways)
│ ├── GENERATE EVENTS (Normal, Special, Domains)
│ ├── CREATIONS REGISTRY (Manage Owned Events, Edit/Delete)
│ ├── STATUS TRACKER (Verify Certificates, Audit ODs)
│ ├── EVENT STATUS CONTROL (Announcements, Status Updates)
│ ├── ATTENDANCE CONSOLE (Mark Student Attendance)
│ ├── PARTICIPANTS console (Download Rosters)
│ ├── PROFILE / LOG OUT
│ └── HOD VERIFICATION (HOD only - Review Departmental Proposals)
└── ADMIN PORTAL ──────► [ADMIN AUTH (Principal / Director / System Admin)]
├── OVERVIEW (System Metrics)
├── VERIFICATION QUEUE (Standard Events, Special Events, Domains)
├── USER MANAGEMENT CONSOLE (Update Profiles, Change Roles)
├── VENUE TRACKER CALENDAR (Visual Schedule & Collision Map)
├── STATUS TRACKER (Audit all college certifications)
├── GLOBAL NOTICES (Institutional notices)
└── PROFILE / LOG OUT
```
### B. High-Fidelity Mermaid Routing Diagram
```mermaid
graph TD
%% Base Gateways
Welcome[Welcome Screen] -->|Select STUDENT| StudentAuth[Student Authentication]
Welcome -->|Select COORDINATOR| FacultyAuth[Faculty / HOD Authentication]
Welcome -->|Select ADMIN| AdminAuth[System Admin Authentication]
%% Student Pathways
StudentAuth -->|Authenticated| StudentDash[Student Dashboard]
StudentDash --> SHome[Home Dashboard]
StudentDash --> SEvents[Events Catalog]
StudentDash --> SRegs[Registrations & QR Tickets]
StudentDash --> STrack[Event Status Tracker]
StudentDash --> SProfile[Student Profile]
SEvents -->|Register & Form/Join Team| STrack
SRegs -->|Verify Status / Upload Certificate| STrack
%% Faculty Pathways
FacultyAuth -->|Select Access Level| FacultyLvl{Access Level}
FacultyLvl -->|Faculty| FacultyOverview[Faculty Dashboard]
FacultyLvl -->|HOD| HODOverview[Faculty Dashboard + HOD Verification]
FacultyOverview --> FHome[Overview & Statistics]
FacultyOverview --> FCreate[Create Event Form]
FacultyOverview --> FCreations[Manage Creations]
FacultyOverview --> FTracker[Status Tracker - Certificate Audit]
FacultyOverview --> FStatus[Event Status & Notices]
FacultyOverview --> FAttendance[Attendance Console]
FacultyOverview --> FParticipants[Participants Console]
FacultyOverview --> FProfile[Faculty Profile]
HODOverview --> HVerify[HOD Verification Panel]
HVerify -->|Accept Department Request| FTracker
HVerify -->|Reject Request| FCreations
%% Admin Pathways
AdminAuth --> AdminOverview[Admin Hub]
AdminOverview --> AHome[System Overview]
AdminOverview --> AVerifyQueue[Verification Queue - Events/Domains/Special]
AdminOverview --> AUsers[User Management Console]
AdminOverview --> AVenue[Venue Tracker Calendar]
AdminOverview --> ATracker[Status Tracker]
AdminOverview --> ANotices[Global Announcements]
AdminOverview --> AProfile[Admin Profile]
%% Cross-Links
FCreations -->|Submit Proposal| HVerify
HVerify -->|Send to Admin Queue| AVerifyQueue
AVerifyQueue -->|Approve Event| SEvents
```
---
## 6. Directory Layout & Components Guide
### File & Component Inventory
* `App.tsx`: Main routing and authentication portal manager. Resolves session state, verifies metadata credentials, performs portal security checks, and loads global states.
* `supabase.ts`: Supabase client configuration, administrative client helper, and helper functions for uploading media (images, certificates) directly to Supabase storage.
* `types.ts`: TypeScript interfaces defining `Event`, `AppState`, `UserRole`, `StudentRequest`, `Ticket`, `ResourcePerson`, `EventSchedule`, `Announcement`, and `UserProfile`.
* `constants.tsx`: Lists static categories, default college domain mappings, clubs, and default showcase events.
* `utils/pdfGenerator.ts`: Handles compiling and exporting the official document proposals for verified events using `jspdf`.
#### Components Directory (`/components/`):
1. `WelcomeScreen.tsx`: Entrance portal page displaying entry gates for students, faculty coordinators, and admins.
2. `LoginForm.tsx`: sliding dual-panel login and sign up form handling internal/external student data collection and role checks.
3. `Dashboard.tsx`: Base container routing student pages.
4. `HomeDashboard.tsx`: Displays active announcements, stats, special events, and links to catalogs.
5. `Hero.tsx`: Dynamic slide search and welcome banner for students.
6. `UpcomingEventsSlider.tsx`: Auto-scrolling showcase slider displaying upcoming events.
7. `AboutHubSection.tsx`: Summary presentation card describing the Events Hub platform.
8. `StatsSection.tsx`: Shows real-time totals of events, participants, and clubs.
9. `AboutSection.tsx`: Institutional overview details, college details, and guidelines.
10. `ContactSection.tsx`: Map coordinates, support addresses, and telephone details for college portals.
11. `EventList.tsx`: Searchable list displaying approved events by domain filter and category.
12. `EventCard.tsx`: Display card for events containing metadata triggers, register commands, and status links.
13. `RegistrationsView.tsx`: Displays registered ticket vouchers. Generates ticket QR code cards capturing registration details, allowing downloading ticket PDFs.
14. `StatusTrackerView.tsx`: visual step progress checker showing the student their certificate review, attendance logs, and OD approvals.
15. `ProfileView.tsx`: Form for students to edit profile settings and view academic transcripts.
16. `AdminLandingPage.tsx`: Base dashboard routing all Coordinator actions.
17. `CreateEventForm.tsx`: Multi-step event proposal compiler managing budgets, itineraries, and quotas.
18. `CreateSpecialEventsView.tsx`: Allows coordinators to publish external program links.
19. `CreateDomainView.tsx`: Form to submit new technical/non-technical domains for college audit.
20. `AdminStatusTrackerView.tsx`: Review board for coordinators/admins to check student certificate uploads, verify details, and approve OD statuses.
21. `AdminEventStatusView.tsx`: Controls notifications, notice updates, and events status transitions.
22. `FacultyParticipantsView.tsx`: Table listing students registered for an event with search/filter.
23. `FacultyAttendanceView.tsx`: Logs session-by-session student attendance check-ins.
24. `FacultyProfileView.tsx`: Base profile for faculty.
25. `FacultyDashboard.tsx`: Console routing Admin actions.
26. `UserManagementView.tsx`: Table displaying all registered users. Admins can edit profile metadata, manually add students, or alter roles.
27. `TicketVerificationView.tsx`: Camera/code scanner allowing organizers to scan student ticket QR codes and update status in real-time.
28. `Footer.tsx`: Institutional styled footer.
29. `PortalAnimation.tsx`: Page transitions and loading animation overlays.

View File

@@ -21,12 +21,10 @@ The system addresses critical administrative bottlenecks such as:
## 2. Technology Stack ## 2. Technology Stack
### Backend ### Backend
* **Core Framework:** Spring Boot 3.2.5 (Java 17) * **Core Backend Service:** Firebase (Firestore Database, Firebase Authentication)
* **API Architecture:** RESTful Controllers * **API Architecture:** Serverless client-side interceptor via `firebaseBackend.ts` which intercepts `/api/*` requests in the browser and processes them directly against Firestore collections.
* **Data Access Layer:** Spring Data JPA with Hibernate * **Database:** Cloud Firestore (Collections: `ems_users`, `ems_events`, `ems_notes`, `ems_classes`)
* **Database:** MySQL (local schema: `ems_db`) * **Security:** Checked via frontend role verification and Firestore Security Rules.
* **Security:** Spring Security (endpoints are permit-all at filter level, but validation and role checks occur inside controller services; password encryption via `BCryptPasswordEncoder`)
* **Developer Tools:** Project Lombok, Spring Boot DevTools
### Frontend ### Frontend
* **Core Framework:** React 19, TypeScript, Vite * **Core Framework:** React 19, TypeScript, Vite
@@ -42,7 +40,7 @@ The system addresses critical administrative bottlenecks such as:
## 3. Database Schema & Architecture ## 3. Database Schema & Architecture
The system contains four principal entities mapped via JPA. The system contains four principal Firestore collections.
### A. User Entity (`users`) ### A. User Entity (`users`)
Holds institutional account profiles, login credentials, permissions, and roles. Holds institutional account profiles, login credentials, permissions, and roles.
@@ -133,37 +131,6 @@ Used for pinning warnings, reminders, or notifications on the main calendar.
## 5. Directory Layout & Architecture ## 5. Directory Layout & Architecture
### Backend Layout
```text
backend/src/main/java/com/ems/backend/
├── BackendApplication.java # Boot Entry Point
├── config/
│ ├── DataInitializer.java # Verify and populate seed users on startup
│ ├── DataSeeder.java # Verify and seed mock events on startup
│ ├── SecurityConfig.java # CORS and filter rules configuration
│ └── CustomUserDetailsService # Loads core user details for Spring Security
├── controller/
│ ├── AdminController.java # CRUD endpoints for Users (/api/admin/users)
│ ├── AuthController.java # Login endpoints (/api/auth)
│ ├── ClassController.java # Class mappings and promotions (/api/classes)
│ ├── EventController.java # Propose, approve, batch-create events (/api/events)
│ ├── HealthController.java # System and database connectivity check (/api/health)
│ └── NoteController.java # Board notices and institutional logs (/api/notes)
├── dto/
│ └── LoginRequest.java # Login DTO container
├── model/
│ ├── BaseEntity.java # Audited fields ID superclass
│ ├── ClassMapping.java # Class config model
│ ├── Event.java # Core event proposal model
│ ├── InstitutionalNote.java # Date-based board notices model
│ └── User.java # Account profiles model
└── repository/
├── ClassRepository.java
├── EventRepository.java # Custom JPA Query for timing conflict resolution
├── InstitutionalNoteRepository.java
└── UserRepository.java
```
### Frontend Layout ### Frontend Layout
```text ```text
frontend/src/ frontend/src/

View File

@@ -1,2 +0,0 @@
/mvnw text eol=lf
*.cmd text eol=crlf

View File

@@ -1,33 +0,0 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@@ -1,3 +0,0 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip

View File

@@ -1,295 +0,0 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
if [ -n "${JAVA_HOME-}" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
scriptDir="$(dirname "$0")"
scriptName="$(basename "$0")"
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
# Find the actual extracted directory name (handles snapshots where filename != directory name)
actualDistributionDir=""
# First try the expected directory name (for regular distributions)
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
actualDistributionDir="$distributionUrlNameMain"
fi
fi
# If not found, search for any directory with the Maven executable (for snapshots)
if [ -z "$actualDistributionDir" ]; then
# enable globbing to iterate over items
set +f
for dir in "$TMP_DOWNLOAD_DIR"/*; do
if [ -d "$dir" ]; then
if [ -f "$dir/bin/$MVN_CMD" ]; then
actualDistributionDir="$(basename "$dir")"
break
fi
fi
done
set -f
fi
if [ -z "$actualDistributionDir" ]; then
verbose "Contents of $TMP_DOWNLOAD_DIR:"
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
die "Could not find Maven distribution directory in extracted archive"
fi
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

View File

@@ -1,189 +0,0 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_M2_PATH = "$HOME/.m2"
if ($env:MAVEN_USER_HOME) {
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
}
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
}
$MAVEN_WRAPPER_DISTS = $null
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
} else {
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
}
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
# Find the actual extracted directory name (handles snapshots where filename != directory name)
$actualDistributionDir = ""
# First try the expected directory name (for regular distributions)
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
$actualDistributionDir = $distributionUrlNameMain
}
# If not found, search for any directory with the Maven executable (for snapshots)
if (!$actualDistributionDir) {
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
if (Test-Path -Path $testPath -PathType Leaf) {
$actualDistributionDir = $_.Name
}
}
}
if (!$actualDistributionDir) {
Write-Error "Could not find Maven distribution directory in extracted archive"
}
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

View File

@@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ems</groupId>
<artifactId>backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend</name>
<description/>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,13 +0,0 @@
package com.ems.backend;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}

View File

@@ -1,31 +0,0 @@
package com.ems.backend.config;
import com.ems.backend.model.User;
import com.ems.backend.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email));
return new org.springframework.security.core.userdetails.User(
user.getEmail(),
user.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole()))
);
}
}

View File

@@ -1,61 +0,0 @@
package com.ems.backend.config;
import com.ems.backend.model.User;
import com.ems.backend.repository.UserRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class DataInitializer {
@Bean
public CommandLineRunner initData(UserRepository userRepository, PasswordEncoder passwordEncoder) {
return args -> {
updateOrCreateUser(userRepository, passwordEncoder, "admin@rit.edu", "admin123", "System Administrator", "ADMIN", "ADMIN");
updateOrCreateUser(userRepository, passwordEncoder, "faculty@rit.edu", "faculty123", "Dr. Faculty Member", "FACULTY", "CSE");
updateOrCreateUser(userRepository, passwordEncoder, "hod@rit.edu", "hod123", "Prof. Head of Dept", "HOD", "AI&ML");
updateOrCreateUser(userRepository, passwordEncoder, "principal@rit.edu", "principal123", "Dr. College Principal", "PRINCIPAL", null);
updateOrCreateUser(userRepository, passwordEncoder, "placement@rit.edu", "placement123", "Placement Coordinator", "PLACEMENT", "Placement Department");
// Newly added users based on request
updateOrCreateUser(userRepository, passwordEncoder, "admin2@rit.edu", "admin123", "Secondary Admin", "ADMIN", "ADMIN");
updateOrCreateUser(userRepository, passwordEncoder, "principal2@rit.edu", "principal123", "Vice Principal", "PRINCIPAL", null);
updateOrCreateUser(userRepository, passwordEncoder, "hod_cse@rit.edu", "hod123", "CSE HOD", "HOD", "CSE");
updateOrCreateUser(userRepository, passwordEncoder, "faculty2@rit.edu", "faculty123", "Assistant Professor CSE", "FACULTY", "CSE");
System.out.println("Demo users verified and updated.");
};
}
private void updateOrCreateUser(UserRepository repo, PasswordEncoder encoder, String email, String pass, String name, String role, String dept) {
User user = repo.findByEmail(email).orElse(new User());
user.setEmail(email);
user.setPassword(encoder.encode(pass));
user.setFullName(name);
user.setRole(role);
user.setDepartment(dept);
// Synchronize flags with roles/departments
if ("PLACEMENT".equals(role) || "Placement Department".equals(dept)) {
user.setPlacementStaff(true);
}
if ("FACULTY".equals(role)) {
user.setClassIncharge(true);
user.setInchargeClass("CSE");
user.setInchargeBatch("3rd Year");
user.setInchargeSection("A");
if (user.getClassStrength() == null || user.getClassStrength() == 0) {
user.setClassStrength(60);
}
}
// Ensure new fields are initialized if new
if (user.getAssignedClubs() == null) {
user.setAssignedClubs(new java.util.ArrayList<>());
}
repo.save(user);
}
}

View File

@@ -1,25 +0,0 @@
package com.ems.backend.config;
import com.ems.backend.model.Event;
import com.ems.backend.model.User;
import com.ems.backend.repository.EventRepository;
import com.ems.backend.repository.UserRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
@Configuration
public class DataSeeder {
@Bean
CommandLineRunner initDatabase(EventRepository eventRepository, UserRepository userRepository) {
return args -> {
System.out.println("DataSeeder: Dummy events seeding disabled.");
};
}
}

View File

@@ -1,57 +0,0 @@
package com.ems.backend.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()
)
.httpBasic(basic -> basic.disable())
.formLogin(form -> form.disable());
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("*")); // For development
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@@ -1,100 +0,0 @@
package com.ems.backend.controller;
import com.ems.backend.model.User;
import com.ems.backend.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/admin")
@CrossOrigin(origins = "*")
public class AdminController {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userRepository.findAll());
}
@PostMapping("/users")
public ResponseEntity<?> createUser(@RequestBody User user) {
if (user == null || user.getEmail() == null || user.getEmail().trim().isEmpty()) {
return ResponseEntity.status(400).body(Map.of("message", "Email is required"));
}
if (user.getFullName() == null || user.getFullName().trim().isEmpty()) {
return ResponseEntity.status(400).body(Map.of("message", "Full name is required"));
}
if (user.getPassword() == null || user.getPassword().trim().isEmpty()) {
return ResponseEntity.status(400).body(Map.of("message", "Password is required"));
}
user.setEmail(user.getEmail().trim().toLowerCase());
user.setFullName(user.getFullName().trim());
if (user.getRole() == null || user.getRole().trim().isEmpty()) {
user.setRole("FACULTY");
}
String role = user.getRole();
if ("HOD".equals(role) || "FACULTY".equals(role)) {
if (user.getDepartment() == null || user.getDepartment().trim().isEmpty()) {
user.setDepartment("H&S Dept");
} else {
user.setDepartment(user.getDepartment().trim());
}
} else {
user.setDepartment("");
}
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
return ResponseEntity.status(400).body(Map.of("message", "User with this email already exists"));
}
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);
return ResponseEntity.ok(user);
}
@PutMapping("/users/{id}")
public ResponseEntity<?> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
User user = userRepository.findById(id).orElseThrow();
user.setFullName(userDetails.getFullName());
user.setEmail(userDetails.getEmail());
user.setRole(userDetails.getRole());
String role = user.getRole();
if ("HOD".equals(role) || "FACULTY".equals(role)) {
user.setDepartment(userDetails.getDepartment() != null ? userDetails.getDepartment().trim() : "");
} else {
user.setDepartment("");
}
user.setClubCoordinator(userDetails.isClubCoordinator());
user.setPlacementStaff(userDetails.isPlacementStaff());
user.setClassIncharge(userDetails.isClassIncharge());
user.setClassStrength(userDetails.getClassStrength());
user.setInchargeClass(userDetails.getInchargeClass());
user.setInchargeBatch(userDetails.getInchargeBatch());
user.setInchargeSection(userDetails.getInchargeSection());
user.setAssignedClubs(userDetails.getAssignedClubs());
if (userDetails.getPassword() != null && !userDetails.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(userDetails.getPassword()));
}
userRepository.save(user);
return ResponseEntity.ok(user);
}
@DeleteMapping("/users/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
userRepository.deleteById(id);
return ResponseEntity.ok(Map.of("message", "User deleted successfully"));
}
}

View File

@@ -1,53 +0,0 @@
package com.ems.backend.controller;
import com.ems.backend.dto.LoginRequest;
import com.ems.backend.model.User;
import com.ems.backend.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/auth")
@CrossOrigin(origins = "*")
public class AuthController {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
Optional<User> userOptional = userRepository.findByEmail(loginRequest.getEmail());
if (userOptional.isPresent()) {
User user = userOptional.get();
if (passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
java.util.Map<String, Object> responseMap = new java.util.HashMap<>();
responseMap.put("id", user.getId());
responseMap.put("email", user.getEmail());
responseMap.put("fullName", user.getFullName());
responseMap.put("role", user.getRole());
responseMap.put("department", user.getDepartment() != null ? user.getDepartment() : "N/A");
responseMap.put("isClubCoordinator", user.isClubCoordinator());
responseMap.put("isPlacementStaff", user.isPlacementStaff());
responseMap.put("isClassIncharge", user.isClassIncharge());
responseMap.put("inchargeClass", user.getInchargeClass());
responseMap.put("inchargeBatch", user.getInchargeBatch());
responseMap.put("inchargeSection", user.getInchargeSection());
responseMap.put("classStrength", user.getClassStrength());
responseMap.put("assignedClubs", user.getAssignedClubs() != null ? user.getAssignedClubs() : java.util.List.of());
return ResponseEntity.ok(responseMap);
}
}
return ResponseEntity.status(401).body(Map.of("message", "Invalid email or passcode"));
}
}

View File

@@ -1,70 +0,0 @@
package com.ems.backend.controller;
import com.ems.backend.model.ClassMapping;
import com.ems.backend.repository.ClassRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/classes")
@CrossOrigin(origins = "*")
public class ClassController {
@Autowired
private ClassRepository classRepository;
@GetMapping
public ResponseEntity<List<ClassMapping>> getAllClasses() {
return ResponseEntity.ok(classRepository.findAll());
}
@PostMapping
public ResponseEntity<?> createClass(@RequestBody ClassMapping classMapping) {
classRepository.save(classMapping);
return ResponseEntity.ok(classMapping);
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteClass(@PathVariable Long id) {
classRepository.deleteById(id);
return ResponseEntity.ok(Map.of("message", "Class mapping deleted successfully"));
}
@PostMapping("/promote")
public ResponseEntity<?> promoteAcademicYear(@RequestParam String institution) {
List<ClassMapping> allClasses = classRepository.findAll();
for (ClassMapping cm : allClasses) {
if (!cm.getInstitution().trim().equalsIgnoreCase(institution.trim())) {
continue;
}
String currentYear = cm.getAcademicYear();
switch (currentYear) {
case "1st Year":
cm.setAcademicYear("2nd Year");
classRepository.save(cm);
break;
case "2nd Year":
cm.setAcademicYear("3rd Year");
classRepository.save(cm);
break;
case "3rd Year":
cm.setAcademicYear("4th Year");
classRepository.save(cm);
break;
case "4th Year":
classRepository.delete(cm);
break;
default:
break;
}
}
return ResponseEntity.ok(Map.of("message", "Academic year promotion completed for " + institution));
}
}

View File

@@ -1,446 +0,0 @@
package com.ems.backend.controller;
import com.ems.backend.model.Event;
import com.ems.backend.model.User;
import com.ems.backend.repository.EventRepository;
import com.ems.backend.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/events")
@CrossOrigin(origins = "*")
public class EventController {
@Autowired
private EventRepository eventRepository;
@Autowired
private UserRepository userRepository;
@PostMapping("/propose")
@Transactional
public ResponseEntity<?> proposeEvent(@RequestBody Map<String, Object> payload) {
try {
Long userId = Long.valueOf(payload.get("userId").toString());
User proposer = userRepository.findById(userId).orElseThrow();
if (!"FACULTY".equals(proposer.getRole()) &&
!"HOD".equals(proposer.getRole()) &&
!"PRINCIPAL".equals(proposer.getRole()) &&
!"PLACEMENT".equals(proposer.getRole()) &&
!"ADMIN".equals(proposer.getRole())) {
return ResponseEntity.status(403).body(Map.of("message", "Only authorized users can propose events"));
}
Event event = new Event();
event.setTitle(payload.get("eventName").toString());
event.setStartDate(parseDateTime(payload.get("startDate")));
event.setEndDate(parseDateTime(payload.get("endDate")));
event.setType(payload.get("eventType") != null ? payload.get("eventType").toString() : "Institutional");
event.setInstitution(payload.get("institution").toString());
event.setDepartment(payload.get("department").toString());
event.setAcademicYears((List<String>) payload.get("academicYears"));
event.setLocation(payload.get("venue").toString());
event.setGuestName(payload.get("guestName") != null ? payload.get("guestName").toString() : null);
event.setGuestSocialProfile(payload.get("socialProfile") != null ? payload.get("socialProfile").toString() : null);
event.setRequirements((List<String>) payload.get("requirements"));
event.setTargetedSections((List<String>) payload.get("targetedSections"));
event.setGroupRequestId(payload.get("groupRequestId") != null ? payload.get("groupRequestId").toString() : null);
// New Fields
event.setDescription(payload.get("description") != null ? payload.get("description").toString() : null);
event.setSponsors((List<String>) payload.get("sponsors"));
String budgetStr = payload.get("budget") != null ? payload.get("budget").toString() : "";
event.setBudget(budgetStr.isEmpty() ? 0.0 : Double.valueOf(budgetStr));
event.setHasRegistrationFee(payload.get("hasRegistrationFee") != null && (boolean) payload.get("hasRegistrationFee"));
String feeStr = payload.get("registrationFee") != null ? payload.get("registrationFee").toString() : "";
event.setRegistrationFee(feeStr.isEmpty() ? 0.0 : Double.valueOf(feeStr));
String category = payload.get("category") != null ? payload.get("category").toString() : "ACADEMIC";
event.setCategory(category);
event.setCentreName(payload.get("centreName") != null ? payload.get("centreName").toString() : null);
if (payload.containsKey("isPublicEvent")) {
event.setPublicEvent((boolean) payload.get("isPublicEvent"));
}
if (payload.containsKey("image")) {
event.setImage(payload.get("image") != null ? payload.get("image").toString() : null);
}
// Workflow Logic
if ("PRINCIPAL".equals(proposer.getRole())) {
event.setStatus("APPROVED");
} else if ("HOD".equals(proposer.getRole()) ||
"CLUB".equals(category) ||
"PLACEMENT".equals(category) ||
"INSTITUTIONAL".equals(category) ||
"PLACEMENT".equals(proposer.getRole()) ||
proposer.isPlacementStaff() ||
"Placement Department".equals(proposer.getDepartment())) {
event.setStatus("PENDING_PR");
} else {
event.setStatus("REQUESTED");
}
event.setProposer(proposer);
if (event.getStartDate().isBefore(LocalDateTime.now())) {
return ResponseEntity.status(400).body(Map.of("message", "Cannot schedule events in the past"));
}
if (event.getEndDate().isBefore(event.getStartDate()) || event.getEndDate().isEqual(event.getStartDate())) {
return ResponseEntity.status(400).body(Map.of("message", "End date must be after start date"));
}
event.setLocation(event.getLocation().trim());
event.setInstitution(event.getInstitution().trim());
// Priority & Conflict Logic
List<Event> conflicts = eventRepository.findConflictingEvents(
event.getInstitution(),
event.getLocation(),
event.getStartDate(),
event.getEndDate(),
-1L,
event.getGroupRequestId()
);
if (!conflicts.isEmpty()) {
if ("PLACEMENT".equals(category) && Boolean.TRUE.equals(payload.get("cancelConflicting"))) {
for (Event conflict : conflicts) {
conflict.setStatus("CANCELLED");
conflict.setRejectionReason("This event is cancelled due to the placement activity happening at the venue at this timing.");
eventRepository.save(conflict);
}
} else {
String conflictMsg = getConflictMessage(event);
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of(
"message", conflictMsg,
"conflicts", conflicts,
"canOverride", "PLACEMENT".equals(category)
));
}
}
eventRepository.save(event);
return ResponseEntity.ok(Map.of("message", "Event proposed successfully", "id", event.getId()));
} catch (Exception e) {
return ResponseEntity.status(400).body(Map.of("message", "Failed to propose event: " + e.getMessage()));
}
}
@GetMapping
public List<Event> getAllEvents() {
List<Event> events = eventRepository.findAll();
populateConflictMessages(events);
return events;
}
@PostMapping("/batch-create")
@Transactional
public ResponseEntity<?> batchCreateEvents(@RequestBody List<Map<String, Object>> eventsPayload) {
try {
if (eventsPayload == null || eventsPayload.isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Map.of("message", "No events provided for import"));
}
List<Event> eventsToSave = new ArrayList<>();
for (Map<String, Object> payload : eventsPayload) {
Event event = new Event();
String title = payload.get("title") != null ? payload.get("title").toString() : "Untitled Event";
event.setTitle(title);
Object dateObj = payload.get("startDate") != null ? payload.get("startDate") : payload.get("finalDate");
if (dateObj == null) {
throw new RuntimeException("Missing date for event: " + title);
}
LocalDateTime start = parseDateTime(dateObj);
event.setStartDate(start);
// Try to calculate duration from original event if available
if (payload.get("startDate") != null && payload.get("endDate") != null) {
try {
LocalDateTime origStart = parseDateTime(payload.get("startDate"));
LocalDateTime origEnd = parseDateTime(payload.get("endDate"));
java.time.Duration duration = java.time.Duration.between(origStart, origEnd);
event.setEndDate(start.plus(duration));
} catch (Exception e) {
event.setEndDate(start.plusHours(2));
}
} else {
event.setEndDate(start.plusHours(2));
}
event.setType(payload.get("type") != null ? payload.get("type").toString() : "Seminar");
event.setInstitution(payload.get("institution") != null ? payload.get("institution").toString().trim() : "RIT");
String dept = payload.get("targetDepartment") != null ? payload.get("targetDepartment").toString() :
(payload.get("department") != null ? payload.get("department").toString() : "General");
event.setDepartment(dept);
Object ay = payload.get("targetBatch") != null ? List.of(payload.get("targetBatch").toString()) : payload.get("academicYears");
event.setAcademicYears((List<String>) ay);
String venue = payload.get("venue") != null ? payload.get("venue").toString() :
(payload.get("location") != null ? payload.get("location").toString() : "TBD");
event.setLocation(venue.trim());
event.setCategory(payload.get("category") != null ? payload.get("category").toString() : "ACADEMIC");
event.setStatus(payload.get("status") != null ? payload.get("status").toString() : "APPROVED");
if (payload.containsKey("requirements")) {
event.setRequirements((List<String>) payload.get("requirements"));
}
if (payload.containsKey("image")) {
event.setImage(payload.get("image") != null ? payload.get("image").toString() : null);
}
if (payload.get("proposer") != null) {
try {
Object proposerObj = payload.get("proposer");
if (proposerObj instanceof Map) {
Map<String, Object> prop = (Map<String, Object>) proposerObj;
Object idObj = prop.get("id");
if (idObj != null) {
Long propId = Long.valueOf(idObj.toString());
userRepository.findById(propId).ifPresent(event::setProposer);
}
}
} catch (Exception e) {
System.err.println("Warning: Could not map proposer for batch event: " + e.getMessage());
}
}
if (event.getStartDate() == null || event.getEndDate() == null) {
throw new RuntimeException("Missing start or end date for event: " + title);
}
if (event.getEndDate().isBefore(event.getStartDate()) || event.getEndDate().isEqual(event.getStartDate())) {
throw new RuntimeException("End date must be after start date for event: " + title);
}
eventsToSave.add(event);
}
for (Event event : eventsToSave) {
String conflictMsg = getConflictMessage(event);
if (conflictMsg != null) {
throw new RuntimeException("Conflict in batch item '" + event.getTitle() + "': " + conflictMsg);
}
}
for (int i = 0; i < eventsToSave.size(); i++) {
Event current = eventsToSave.get(i);
for (int j = i + 1; j < eventsToSave.size(); j++) {
Event other = eventsToSave.get(j);
if (isVenueTimingConflict(current, other)) {
throw new RuntimeException("Conflict between imported events '" + current.getTitle() + "' and '" + other.getTitle() + "' at " + current.getLocation());
}
}
}
for (Event event : eventsToSave) {
eventRepository.save(event);
}
return ResponseEntity.ok(Map.of("message", "Batch events created successfully", "count", eventsToSave.size()));
} catch (Exception e) {
String errorMsg = e.getMessage() != null ? e.getMessage() : "Unknown error during batch creation";
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Map.of("message", "Failed to create batch events: " + errorMsg));
}
}
@PostMapping("/{id}/approve")
@Transactional
public ResponseEntity<?> approveEvent(@PathVariable Long id, @RequestParam Long userId) {
User user = userRepository.findById(userId).orElseThrow();
Event event = eventRepository.findById(id).orElseThrow();
String conflictMsg = getConflictMessage(event);
if (conflictMsg != null) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of("message", conflictMsg));
}
if ("HOD".equals(user.getRole())) {
event.setStatus("PENDING_PR");
} else if ("PRINCIPAL".equals(user.getRole())) {
event.setStatus("APPROVED");
} else {
return ResponseEntity.status(403).body(Map.of("message", "Only HoD or Principal can approve events"));
}
eventRepository.save(event);
return ResponseEntity.ok(Map.of("message", "Event action completed successfully", "status", event.getStatus()));
}
@PostMapping("/{id}/reject")
public ResponseEntity<?> rejectEvent(@PathVariable Long id, @RequestParam Long userId, @RequestBody(required = false) Map<String, String> payload) {
User user = userRepository.findById(userId).orElseThrow();
Event event = eventRepository.findById(id).orElseThrow();
String reason = payload != null ? payload.get("reason") : null;
if (reason == null || reason.trim().isEmpty()) {
return ResponseEntity.status(400).body(Map.of("message", "Rejection reason is mandatory"));
}
if ("HOD".equals(user.getRole())) {
event.setStatus("HOD_REJECTED");
} else if ("PRINCIPAL".equals(user.getRole())) {
event.setStatus("PRINCIPAL_REJECTED");
} else {
return ResponseEntity.status(403).body(Map.of("message", "Only HoD or Principal can reject events"));
}
event.setRejectionReason(reason);
eventRepository.save(event);
return ResponseEntity.ok(Map.of("message", "Event rejected successfully", "status", event.getStatus()));
}
@PutMapping("/{id}")
@Transactional
public ResponseEntity<?> updateEvent(@PathVariable Long id, @RequestBody Map<String, Object> payload) {
try {
Event event = eventRepository.findById(id).orElseThrow();
Long userId = Long.valueOf(payload.get("userId").toString());
User user = userRepository.findById(userId).orElseThrow();
boolean isAdmin = "ADMIN".equals(user.getRole());
if (!isAdmin) {
if (!event.getProposer().getId().equals(userId)) {
return ResponseEntity.status(403).body(Map.of("message", "Only the proposer can edit this event"));
}
if (!"REQUESTED".equals(event.getStatus())) {
return ResponseEntity.status(403).body(Map.of("message", "Event cannot be edited once it moves past the initial request stage"));
}
}
if (payload.containsKey("title")) event.setTitle(payload.get("title").toString());
if (payload.containsKey("startDate")) event.setStartDate(parseDateTime(payload.get("startDate")));
if (payload.containsKey("endDate")) event.setEndDate(parseDateTime(payload.get("endDate")));
if (payload.containsKey("eventType")) event.setType(payload.get("eventType").toString());
if (payload.containsKey("institution")) event.setInstitution(payload.get("institution").toString());
if (payload.containsKey("department")) event.setDepartment(payload.get("department").toString());
if (payload.containsKey("venue")) event.setLocation(payload.get("venue").toString());
if (payload.containsKey("guestName")) event.setGuestName(payload.get("guestName").toString());
if (payload.containsKey("socialProfile")) event.setGuestSocialProfile(payload.get("socialProfile").toString());
if (payload.containsKey("academicYears")) event.setAcademicYears((List<String>) payload.get("academicYears"));
if (payload.containsKey("targetedSections")) event.setTargetedSections((List<String>) payload.get("targetedSections"));
if (payload.containsKey("requirements")) event.setRequirements((List<String>) payload.get("requirements"));
if (payload.containsKey("sponsors")) event.setSponsors((List<String>) payload.get("sponsors"));
if (payload.containsKey("description")) event.setDescription(payload.get("description").toString());
if (payload.containsKey("budget")) {
String budgetStr = payload.get("budget").toString();
event.setBudget(budgetStr.isEmpty() ? 0.0 : Double.valueOf(budgetStr));
}
if (payload.containsKey("hasRegistrationFee")) event.setHasRegistrationFee((boolean) payload.get("hasRegistrationFee"));
if (payload.containsKey("registrationFee")) {
String feeStr = payload.get("registrationFee").toString();
event.setRegistrationFee(feeStr.isEmpty() ? 0.0 : Double.valueOf(feeStr));
}
if (payload.containsKey("centreName")) event.setCentreName(payload.get("centreName").toString());
if (payload.containsKey("isPublicEvent")) event.setPublicEvent((boolean) payload.get("isPublicEvent"));
if (payload.containsKey("image")) {
event.setImage(payload.get("image") != null ? payload.get("image").toString() : null);
}
if (payload.containsKey("status") && isAdmin) {
event.setStatus(payload.get("status").toString());
}
if (event.getEndDate().isBefore(event.getStartDate()) || event.getEndDate().isEqual(event.getStartDate())) {
return ResponseEntity.status(400).body(Map.of("message", "End date must be after start date"));
}
event.setLocation(event.getLocation().trim());
event.setInstitution(event.getInstitution().trim());
String conflictMsg = getConflictMessage(event);
if (conflictMsg != null) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of("message", conflictMsg));
}
eventRepository.save(event);
return ResponseEntity.ok(Map.of("message", "Event updated successfully"));
} catch (Exception e) {
return ResponseEntity.status(400).body(Map.of("message", "Failed to update event: " + e.getMessage()));
}
}
private void populateConflictMessages(List<Event> events) {
for (Event event : events) {
if (!"APPROVED".equals(event.getStatus()) && !"COMPLETED".equals(event.getStatus()) && !"CANCELLED".equals(event.getStatus())) {
event.setConflictMessage(getConflictMessage(event));
}
}
}
private String getConflictMessage(Event event) {
LocalDateTime start = event.getStartDate();
LocalDateTime end = event.getEndDate();
String location = event.getLocation().trim();
String institution = event.getInstitution().trim();
List<Event> conflicts = eventRepository.findConflictingEvents(
institution,
location,
start,
end,
event.getId() != null ? event.getId() : -1L,
event.getGroupRequestId()
);
if (!conflicts.isEmpty()) {
Event conflict = conflicts.get(0);
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
return String.format("Venue Conflict: '%s' is already booked for '%s' from %s to %s",
location, conflict.getTitle(),
conflict.getStartDate().format(timeFormatter),
conflict.getEndDate().format(timeFormatter));
}
return null;
}
private boolean isVenueTimingConflict(Event first, Event second) {
if ("CANCELLED".equals(first.getStatus()) || "CANCELLED".equals(second.getStatus())) {
return false;
}
return first.getInstitution().trim().equals(second.getInstitution().trim())
&& first.getLocation().trim().equalsIgnoreCase(second.getLocation().trim())
&& first.getStartDate().isBefore(second.getEndDate())
&& first.getEndDate().isAfter(second.getStartDate());
}
private LocalDateTime parseDateTime(Object value) {
if (value == null) return null;
String str = value.toString().trim();
if (str.isEmpty()) return null;
str = str.replace(" ", "T");
if (str.length() == 10) str += "T00:00:00";
if (str.length() == 16) str += ":00";
if (str.contains("+")) str = str.substring(0, str.indexOf("+"));
if (str.contains("Z")) str = str.replace("Z", "");
if (str.contains(".")) str = str.substring(0, str.indexOf("."));
try {
return LocalDateTime.parse(str);
} catch (Exception e) {
System.err.println("Failed to parse date: " + str);
throw e;
}
}
}

View File

@@ -1,34 +0,0 @@
package com.ems.backend.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Map;
@RestController
public class HealthController {
@Autowired
private DataSource dataSource;
@GetMapping("/api/health")
public Map<String, Object> health() {
String dbStatus = "DOWN";
try (Connection connection = dataSource.getConnection()) {
if (connection.isValid(1)) {
dbStatus = "UP";
}
} catch (Exception e) {
dbStatus = "DOWN: " + e.getMessage();
}
return Map.of(
"status", "UP",
"message", "EMS Backend is running",
"database", dbStatus
);
}
}

View File

@@ -1,27 +0,0 @@
package com.ems.backend.controller;
import com.ems.backend.model.InstitutionalNote;
import com.ems.backend.repository.InstitutionalNoteRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/notes")
@CrossOrigin(origins = "*")
public class NoteController {
@Autowired
private InstitutionalNoteRepository noteRepository;
@GetMapping
public List<InstitutionalNote> getAllNotes() {
return noteRepository.findAll();
}
@PostMapping
public InstitutionalNote createNote(@RequestBody InstitutionalNote note) {
return noteRepository.save(note);
}
}

View File

@@ -1,13 +0,0 @@
package com.ems.backend.dto;
import lombok.Data;
public class LoginRequest {
private String email;
private String password;
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}

View File

@@ -1,36 +0,0 @@
package com.ems.backend.model;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@CreationTimestamp
@Column(updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
public BaseEntity() {}
public BaseEntity(Long id, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

View File

@@ -1,44 +0,0 @@
package com.ems.backend.model;
import jakarta.persistence.*;
import lombok.*;
import java.util.List;
@Entity
@Table(name = "classes")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ClassMapping extends BaseEntity {
@Column(nullable = false)
private String institution; // RIT, RSB
@Column(nullable = false)
private String department;
@Column(nullable = false)
private String academicYear; // 1st Year, 2nd Year, etc.
@ElementCollection
@CollectionTable(name = "class_sections", joinColumns = @JoinColumn(name = "class_id"))
@Column(name = "section_name")
private List<String> sections;
@Builder.Default
private String status = "Ready";
public String getInstitution() { return institution; }
public void setInstitution(String institution) { this.institution = institution; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getAcademicYear() { return academicYear; }
public void setAcademicYear(String academicYear) { this.academicYear = academicYear; }
public java.util.List<String> getSections() { return sections; }
public void setSections(java.util.List<String> sections) { this.sections = sections; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
}

View File

@@ -1,218 +0,0 @@
package com.ems.backend.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "events")
public class Event extends BaseEntity {
@Column(nullable = false)
private String title;
@Column(columnDefinition = "TEXT")
private String description;
@Column(nullable = false)
private LocalDateTime startDate;
@Column(nullable = false)
private LocalDateTime endDate;
@Column(nullable = false)
private String location;
@Column(nullable = false)
private String category; // ACADEMIC, CLUB, etc.
@Column(nullable = false)
private String type; // Workshop, Seminar, etc.
@Column(nullable = false)
private String institution; // RIT, RSB
@Column(nullable = false)
private String department;
@jakarta.persistence.ElementCollection
private List<String> academicYears;
@Column(nullable = false)
private String status; // PENDING, APPROVED, COMPLETED, CANCELLED
private String guestName;
private String guestSocialProfile;
@jakarta.persistence.ElementCollection
private List<String> requirements;
@jakarta.persistence.ElementCollection
private List<String> targetedSections;
private Double budget;
@Column(columnDefinition = "boolean default false")
private boolean hasRegistrationFee;
private Double registrationFee;
@jakarta.persistence.ElementCollection
private List<String> sponsors;
@jakarta.persistence.ManyToOne
@jakarta.persistence.JoinColumn(name = "user_id")
private User proposer;
@Column(columnDefinition = "TEXT")
private String rejectionReason;
private String groupRequestId;
private String centreName;
@Column(columnDefinition = "boolean default false")
private boolean isPublicEvent;
@Column(columnDefinition = "LONGTEXT")
private String image;
@jakarta.persistence.Transient
private String conflictMessage;
public Event() {}
public Event(String title, String description, LocalDateTime startDate, LocalDateTime endDate, String location, String category, String type, String institution, String department, List<String> academicYears, String status, String guestName, String guestSocialProfile, List<String> requirements, List<String> targetedSections, Double budget, boolean hasRegistrationFee, Double registrationFee, User proposer, String centreName, boolean isPublicEvent) {
this.title = title;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
this.location = location;
this.category = category;
this.type = type;
this.institution = institution;
this.department = department;
this.academicYears = academicYears;
this.status = status;
this.guestName = guestName;
this.guestSocialProfile = guestSocialProfile;
this.requirements = requirements;
this.targetedSections = targetedSections;
this.budget = budget;
this.hasRegistrationFee = hasRegistrationFee;
this.registrationFee = registrationFee;
this.proposer = proposer;
this.centreName = centreName;
this.isPublicEvent = isPublicEvent;
}
// Manual Getters and Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public LocalDateTime getStartDate() { return startDate; }
public void setStartDate(LocalDateTime startDate) { this.startDate = startDate; }
public LocalDateTime getEndDate() { return endDate; }
public void setEndDate(LocalDateTime endDate) { this.endDate = endDate; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getInstitution() { return institution; }
public void setInstitution(String institution) { this.institution = institution; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public List<String> getAcademicYears() { return academicYears; }
public void setAcademicYears(List<String> academicYears) { this.academicYears = academicYears; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public String getGuestName() { return guestName; }
public void setGuestName(String guestName) { this.guestName = guestName; }
public String getGuestSocialProfile() { return guestSocialProfile; }
public void setGuestSocialProfile(String guestSocialProfile) { this.guestSocialProfile = guestSocialProfile; }
public List<String> getRequirements() { return requirements; }
public void setRequirements(List<String> requirements) { this.requirements = requirements; }
public List<String> getTargetedSections() { return targetedSections; }
public void setTargetedSections(List<String> targetedSections) { this.targetedSections = targetedSections; }
public Double getBudget() { return budget; }
public void setBudget(Double budget) { this.budget = budget; }
public boolean isHasRegistrationFee() { return hasRegistrationFee; }
public void setHasRegistrationFee(boolean hasRegistrationFee) { this.hasRegistrationFee = hasRegistrationFee; }
public Double getRegistrationFee() { return registrationFee; }
public void setRegistrationFee(Double registrationFee) { this.registrationFee = registrationFee; }
public List<String> getSponsors() { return sponsors; }
public void setSponsors(List<String> sponsors) { this.sponsors = sponsors; }
public User getProposer() { return proposer; }
public void setProposer(User proposer) { this.proposer = proposer; }
public String getRejectionReason() { return rejectionReason; }
public void setRejectionReason(String rejectionReason) { this.rejectionReason = rejectionReason; }
public String getGroupRequestId() { return groupRequestId; }
public void setGroupRequestId(String groupRequestId) { this.groupRequestId = groupRequestId; }
public String getConflictMessage() { return conflictMessage; }
public void setConflictMessage(String conflictMessage) { this.conflictMessage = conflictMessage; }
public String getCentreName() {
return centreName;
}
public void setCentreName(String centreName) {
this.centreName = centreName;
}
public boolean isPublicEvent() {
return isPublicEvent;
}
public void setPublicEvent(boolean publicEvent) {
isPublicEvent = publicEvent;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
// Manual Builder
public static EventBuilder builder() {
return new EventBuilder();
}
public static class EventBuilder {
private Event event = new Event();
public EventBuilder title(String title) { event.setTitle(title); return this; }
public EventBuilder description(String description) { event.setDescription(description); return this; }
public EventBuilder startDate(LocalDateTime startDate) { event.setStartDate(startDate); return this; }
public EventBuilder endDate(LocalDateTime endDate) { event.setEndDate(endDate); return this; }
public EventBuilder location(String location) { event.setLocation(location); return this; }
public EventBuilder category(String category) { event.setCategory(category); return this; }
public EventBuilder type(String type) { event.setType(type); return this; }
public EventBuilder institution(String institution) { event.setInstitution(institution); return this; }
public EventBuilder department(String department) { event.setDepartment(department); return this; }
public EventBuilder academicYears(List<String> academicYears) { event.setAcademicYears(academicYears); return this; }
public EventBuilder status(String status) { event.setStatus(status); return this; }
public EventBuilder guestName(String guestName) { event.setGuestName(guestName); return this; }
public EventBuilder guestSocialProfile(String guestSocialProfile) { event.setGuestSocialProfile(guestSocialProfile); return this; }
public EventBuilder requirements(List<String> requirements) { event.setRequirements(requirements); return this; }
public EventBuilder targetedSections(List<String> targetedSections) { event.setTargetedSections(targetedSections); return this; }
public EventBuilder budget(Double budget) { event.setBudget(budget); return this; }
public EventBuilder hasRegistrationFee(boolean hasRegistrationFee) { event.setHasRegistrationFee(hasRegistrationFee); return this; }
public EventBuilder registrationFee(Double registrationFee) { event.setRegistrationFee(registrationFee); return this; }
public EventBuilder sponsors(List<String> sponsors) { event.setSponsors(sponsors); return this; }
public EventBuilder proposer(User proposer) { event.setProposer(proposer); return this; }
public EventBuilder groupRequestId(String groupRequestId) { event.setGroupRequestId(groupRequestId); return this; }
public EventBuilder image(String image) { event.setImage(image); return this; }
public Event build() {
return event;
}
}
}

View File

@@ -1,31 +0,0 @@
package com.ems.backend.model;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Entity
@Data
@Table(name = "institutional_notes")
public class InstitutionalNote {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String content;
@Column(nullable = false)
private LocalDate targetDate;
private String authorName;
private String authorEmail;
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
}

View File

@@ -1,94 +0,0 @@
package com.ems.backend.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
@Entity
@Table(name = "users")
public class User extends BaseEntity {
@Column(unique = true, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String fullName;
@Column(nullable = false)
private String role; // FACULTY, HOD, PRINCIPAL
private String department;
@Column(columnDefinition = "boolean default false")
@JsonProperty("isClubCoordinator")
private boolean isClubCoordinator;
@Column(columnDefinition = "boolean default false")
@JsonProperty("isPlacementStaff")
private boolean isPlacementStaff;
@Column(columnDefinition = "boolean default false")
@JsonProperty("isClassIncharge")
private boolean isClassIncharge;
@Column
private Integer classStrength;
@Column
private String inchargeClass;
@Column
private String inchargeBatch;
@Column
private String inchargeSection;
@jakarta.persistence.ElementCollection
@JsonProperty("assignedClubs")
private List<String> assignedClubs;
public User() {}
public User(String email, String password, String fullName, String role, String department, boolean isClubCoordinator, boolean isPlacementStaff, List<String> assignedClubs) {
this.email = email;
this.password = password;
this.fullName = fullName;
this.role = role;
this.department = department;
this.isClubCoordinator = isClubCoordinator;
this.isPlacementStaff = isPlacementStaff;
this.assignedClubs = assignedClubs;
}
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getFullName() { return fullName; }
public void setFullName(String fullName) { this.fullName = fullName; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public boolean isClubCoordinator() { return isClubCoordinator; }
public void setClubCoordinator(boolean clubCoordinator) { isClubCoordinator = clubCoordinator; }
public boolean isPlacementStaff() { return isPlacementStaff; }
public void setPlacementStaff(boolean placementStaff) { isPlacementStaff = placementStaff; }
public boolean isClassIncharge() { return isClassIncharge; }
public void setClassIncharge(boolean classIncharge) { isClassIncharge = classIncharge; }
public Integer getClassStrength() { return classStrength; }
public void setClassStrength(Integer classStrength) { this.classStrength = classStrength; }
public String getInchargeClass() { return inchargeClass; }
public void setInchargeClass(String inchargeClass) { this.inchargeClass = inchargeClass; }
public String getInchargeBatch() { return inchargeBatch; }
public void setInchargeBatch(String inchargeBatch) { this.inchargeBatch = inchargeBatch; }
public String getInchargeSection() { return inchargeSection; }
public void setInchargeSection(String inchargeSection) { this.inchargeSection = inchargeSection; }
public List<String> getAssignedClubs() { return assignedClubs; }
public void setAssignedClubs(List<String> assignedClubs) { this.assignedClubs = assignedClubs; }
}

View File

@@ -1,9 +0,0 @@
package com.ems.backend.repository;
import com.ems.backend.model.ClassMapping;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ClassRepository extends JpaRepository<ClassMapping, Long> {
}

View File

@@ -1,26 +0,0 @@
package com.ems.backend.repository;
import com.ems.backend.model.Event;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface EventRepository extends JpaRepository<Event, Long> {
List<Event> findByCategory(String category);
List<Event> findByStatus(String status);
@Query("SELECT e FROM Event e WHERE e.status = 'APPROVED' AND e.institution = :institution AND e.location = :location " +
"AND e.startDate < :endDate AND e.endDate > :startDate AND e.id != :eventId " +
"AND (e.groupRequestId IS NULL OR :groupRequestId IS NULL OR e.groupRequestId != :groupRequestId)")
List<Event> findConflictingEvents(@Param("institution") String institution,
@Param("location") String location,
@Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate,
@Param("eventId") Long eventId,
@Param("groupRequestId") String groupRequestId);
}

View File

@@ -1,10 +0,0 @@
package com.ems.backend.repository;
import com.ems.backend.model.InstitutionalNote;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
public interface InstitutionalNoteRepository extends JpaRepository<InstitutionalNote, Long> {
List<InstitutionalNote> findByTargetDate(LocalDate targetDate);
}

View File

@@ -1,12 +0,0 @@
package com.ems.backend.repository;
import com.ems.backend.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}

View File

@@ -1,14 +0,0 @@
server.port=8081
spring.application.name=backend
# Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/ems_db?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=Abiram@07
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA Configuration
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

View File

@@ -1,13 +0,0 @@
package com.ems.backend;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BackendApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -132,58 +132,17 @@ async function seedDatabaseIfEmpty() {
} }
} }
// Clean up existing dummy events with "Sample event for" description
const eventsSnap = await getDocs(collection(db, 'ems_events')); const eventsSnap = await getDocs(collection(db, 'ems_events'));
if (eventsSnap.empty) { for (const d of eventsSnap.docs) {
console.log("[Firebase Backend] Seeding events..."); const data = d.data();
const venues = [ if (data.description && data.description.startsWith("Sample event for")) {
"GB 4th floor auditorium", console.log("[Firebase Backend] Deleting dummy event:", data.title);
"Wozniak Auditorium", await deleteDoc(d.ref);
"C6-02 Indoor Theatre",
"H Block Guest Lecture Theatre"
];
const categories = ["ACADEMIC", "CLUB", "PLACEMENT", "SPORTS"];
const types = ["Workshop", "Seminar", "Guest Lecture", "Competition"];
const statuses = ["REQUESTED", "APPROVED", "COMPLETED"];
let evId = 100;
const now = new Date();
for (let month = now.getMonth() - 2; month <= now.getMonth() + 2; month++) {
const targetMonth = (month + 12) % 12;
const targetYear = now.getFullYear() + (month < 0 ? -1 : (month > 11 ? 1 : 0));
for (const dept of ["CSE", "AI&ML"]) {
const start = new Date(targetYear, targetMonth, 15, 10, 0);
const end = new Date(targetYear, targetMonth, 15, 13, 0);
const venue = venues[Math.floor(Math.random() * venues.length)];
const cat = categories[Math.floor(Math.random() * categories.length)];
const type = types[Math.floor(Math.random() * types.length)];
const status = statuses[Math.floor(Math.random() * statuses.length)];
const event = {
id: evId++,
title: `${dept} ${type} - ${targetMonth + 1}/15`,
description: `Sample event for ${dept} department.`,
startDate: start.toISOString(),
endDate: end.toISOString(),
location: venue,
category: cat,
type: type,
institution: "RIT",
department: dept,
academicYears: ["3rd Year"],
status: status,
budget: 5000,
hasRegistrationFee: false,
registrationFee: 0,
proposer: { id: 2, fullName: "Dr. Faculty Member", email: "faculty@rit.edu", role: "FACULTY" }
};
await setDoc(doc(db, 'ems_events', String(event.id)), event);
}
} }
} }
} catch (err) { } catch (err) {
console.error("Seeding failed:", err); console.error("Cleanup failed:", err);
} }
} }
@@ -678,6 +637,7 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
registrationFee: feeStr === "" ? 0.0 : parseFloat(feeStr), registrationFee: feeStr === "" ? 0.0 : parseFloat(feeStr),
centreName: payload.centreName || null, centreName: payload.centreName || null,
isPublicEvent: !!payload.isPublicEvent, isPublicEvent: !!payload.isPublicEvent,
image: payload.image || null,
proposer: { proposer: {
id: proposer.id, id: proposer.id,
fullName: proposer.fullName, fullName: proposer.fullName,
@@ -740,6 +700,7 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
academicYears: ay, academicYears: ay,
status: payload.status || "APPROVED", status: payload.status || "APPROVED",
requirements: payload.requirements || [], requirements: payload.requirements || [],
image: payload.image || null,
proposer proposer
}; };
@@ -925,6 +886,7 @@ window.fetch = async function(input: RequestInfo | URL, init?: RequestInit): Pro
} }
if (payload.centreName !== undefined) updatedFields.centreName = payload.centreName; if (payload.centreName !== undefined) updatedFields.centreName = payload.centreName;
if (payload.isPublicEvent !== undefined) updatedFields.isPublicEvent = !!payload.isPublicEvent; if (payload.isPublicEvent !== undefined) updatedFields.isPublicEvent = !!payload.isPublicEvent;
if (payload.image !== undefined) updatedFields.image = payload.image;
if (payload.status !== undefined && isAdmin) { if (payload.status !== undefined && isAdmin) {
updatedFields.status = payload.status; updatedFields.status = payload.status;
} }