feat(notes-pyqs): implement dynamic notes scanning, subject dropdown, and redesign UI

This commit is contained in:
Amudieshwar A G
2026-07-23 11:01:53 +05:30
parent 0209657182
commit 05bf810bc1
92 changed files with 26468 additions and 137 deletions

View File

@@ -1,12 +1,23 @@
package com.rit.portal.config;
import com.rit.portal.entity.NotePyq;
import com.rit.portal.entity.BusRoute;
import com.rit.portal.entity.BusStop;
import com.rit.portal.repository.NotePyqRepository;
import com.rit.portal.repository.BusRouteRepository;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.rit.portal.controller.NotePyqController;
@Component
public class DataInitializer implements CommandLineRunner {
@@ -14,100 +25,69 @@ public class DataInitializer implements CommandLineRunner {
@Autowired
private NotePyqRepository noteRepository;
@Autowired
private BusRouteRepository busRouteRepository;
@Autowired
private NotePyqController notePyqController;
@Override
public void run(String... args) throws Exception {
if (noteRepository.count() == 0) {
noteRepository.saveAll(Arrays.asList(
NotePyq.builder()
.title("Engineering Mathematics Unit 1-5")
.subject("Mathematics")
.department("Computer Science & Engineering")
.semester(1)
.fileType("notes")
.downloadUrl("#")
.fileSize("4.2 MB")
.downloadsCount(348)
.uploadedAt(LocalDateTime.now())
.build(),
NotePyq.builder()
.title("Physics PYQ 2020-2024")
.subject("Physics")
.department("Computer Science & Engineering")
.semester(1)
.fileType("pyq")
.downloadUrl("#")
.fileSize("8.1 MB")
.downloadsCount(512)
.uploadedAt(LocalDateTime.now())
.build(),
NotePyq.builder()
.title("C Programming Complete Notes")
.subject("Programming")
.department("Computer Science & Engineering")
.semester(1)
.fileType("notes")
.downloadUrl("#")
.fileSize("6.3 MB")
.downloadsCount(734)
.uploadedAt(LocalDateTime.now())
.build(),
NotePyq.builder()
.title("Data Structures PYQ 2019-2024")
.subject("Data Structures")
.department("Computer Science & Engineering")
.semester(3)
.fileType("pyq")
.downloadUrl("#")
.fileSize("10.2 MB")
.downloadsCount(621)
.uploadedAt(LocalDateTime.now())
.build(),
NotePyq.builder()
.title("Circuit Theory Full Notes")
.subject("Circuit Theory")
.department("Electronics & Communication")
.semester(2)
.fileType("notes")
.downloadUrl("#")
.fileSize("5.7 MB")
.downloadsCount(289)
.uploadedAt(LocalDateTime.now())
.build(),
NotePyq.builder()
.title("Anna University Syllabus 2021")
.subject("Syllabus")
.department("All Departments")
.semester(1)
.fileType("syllabus")
.downloadUrl("#")
.fileSize("2.1 MB")
.downloadsCount(890)
.uploadedAt(LocalDateTime.now())
.build(),
NotePyq.builder()
.title("Thermodynamics Notes")
.subject("Thermodynamics")
.department("Mechanical Engineering")
.semester(3)
.fileType("notes")
.downloadUrl("#")
.fileSize("3.9 MB")
.downloadsCount(201)
.uploadedAt(LocalDateTime.now())
.build(),
NotePyq.builder()
.title("Digital Electronics PYQ")
.subject("Digital Electronics")
.department("Electronics & Communication")
.semester(4)
.fileType("pyq")
.downloadUrl("#")
.fileSize("7.3 MB")
.downloadsCount(445)
.uploadedAt(LocalDateTime.now())
.build()
));
System.out.println("🌱 Database successfully seeded with Notes & PYQs test data!");
// Clear all previous mock data from the database
noteRepository.deleteAll();
// Run initial scan to populate database with whatever is currently in /uploads
try {
notePyqController.syncUploads();
System.out.println("🌱 Initialized Note/PYQ database from local uploads folder!");
} catch (Exception e) {
System.err.println("⚠️ Failed to run initial uploads sync: " + e.getMessage());
}
// Seed Bus Routes
if (busRouteRepository.count() == 0) {
try {
ObjectMapper mapper = new ObjectMapper();
InputStream is = getClass().getResourceAsStream("/bus_routes.json");
if (is != null) {
List<Map<String, Object>> routesList = mapper.readValue(is, new TypeReference<List<Map<String, Object>>>() {});
List<BusRoute> routesToSave = new ArrayList<>();
for (Map<String, Object> routeMap : routesList) {
BusRoute br = BusRoute.builder()
.number((String) routeMap.get("number"))
.name((String) routeMap.get("name"))
.from((String) routeMap.get("from"))
.to((String) routeMap.get("to"))
.departureTime((String) routeMap.get("departureTime"))
.arrivalTime((String) routeMap.get("arrivalTime"))
.color((String) routeMap.get("color"))
.build();
List<Map<String, String>> stopsList = (List<Map<String, String>>) routeMap.get("stops");
List<BusStop> stops = new ArrayList<>();
if (stopsList != null) {
for (int i = 0; i < stopsList.size(); i++) {
Map<String, String> stopMap = stopsList.get(i);
stops.add(BusStop.builder()
.route(br)
.name(stopMap.get("name"))
.time(stopMap.get("time"))
.stopOrder(i + 1)
.build());
}
}
br.setStops(stops);
routesToSave.add(br);
}
busRouteRepository.saveAll(routesToSave);
System.out.println("🌱 Database successfully seeded with " + routesToSave.size() + " Bus Routes and stops!");
} else {
System.err.println("⚠️ Could not find bus_routes.json in resources!");
}
} catch (Exception e) {
System.err.println("❌ Failed to seed bus routes: " + e.getMessage());
e.printStackTrace();
}
}
}
}

View File

@@ -9,10 +9,16 @@ public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173", "http://localhost:3000")
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
@Override
public void addResourceHandlers(org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry registry) {
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:uploads/");
}
}

View File

@@ -7,6 +7,11 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/api/notes")
@CrossOrigin(origins = "*") // CrossOrigin configured globally, but added here for safety
@@ -15,21 +20,117 @@ public class NotePyqController {
@Autowired
private NotePyqRepository noteRepository;
// Sync uploads directory with DB
public synchronized void syncUploads() {
File uploadsDir = new File("uploads");
if (!uploadsDir.exists()) {
uploadsDir.mkdirs();
}
List<NotePyq> currentNotes = noteRepository.findAll();
List<String> activeUrls = Collections.synchronizedList(new ArrayList<>());
File[] semDirs = uploadsDir.listFiles();
if (semDirs != null) {
for (File semDir : semDirs) {
if (semDir.isDirectory() && semDir.getName().startsWith("sem")) {
String semStr = semDir.getName().substring(3);
try {
Integer semester = Integer.parseInt(semStr);
File[] subDirs = semDir.listFiles();
if (subDirs != null) {
for (File subDir : subDirs) {
if (subDir.isDirectory()) {
String subject = subDir.getName();
File[] typeDirs = subDir.listFiles();
if (typeDirs != null) {
for (File typeDir : typeDirs) {
if (typeDir.isDirectory()) {
String fileType = typeDir.getName();
File[] files = typeDir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && !file.getName().equals(".gitkeep")) {
String fileName = file.getName();
String downloadUrl = "http://localhost:8080/uploads/" +
semDir.getName() + "/" +
subDir.getName() + "/" +
typeDir.getName() + "/" +
fileName;
activeUrls.add(downloadUrl);
boolean exists = currentNotes.stream().anyMatch(n -> n.getDownloadUrl().equals(downloadUrl));
if (!exists) {
String baseName = fileName.contains(".") ? fileName.substring(0, fileName.lastIndexOf('.')) : fileName;
String title = baseName.replace("_", " ").replace("-", " ");
String department = "All Departments";
long bytes = file.length();
String fileSize = formatFileSize(bytes);
NotePyq newNote = NotePyq.builder()
.title(title)
.subject(subject)
.department(department)
.semester(semester)
.fileType(fileType)
.downloadUrl(downloadUrl)
.fileSize(fileSize)
.downloadsCount(0)
.uploadedAt(LocalDateTime.now())
.build();
noteRepository.save(newNote);
}
}
}
}
}
}
}
}
}
}
} catch (NumberFormatException e) {
// Ignore
}
}
}
}
// Delete notes from DB that no longer exist on disk
for (NotePyq note : currentNotes) {
if (!activeUrls.contains(note.getDownloadUrl())) {
noteRepository.delete(note);
}
}
}
private String formatFileSize(long bytes) {
if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024));
char pre = "KMGTPE".charAt(exp - 1);
return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
}
// Get all notes
@GetMapping
public List<NotePyq> getAllNotes() {
syncUploads();
return noteRepository.findAll();
}
// Get notes by semester
@GetMapping("/semester/{semester}")
public List<NotePyq> getNotesBySemester(@PathVariable Integer semester) {
syncUploads();
return noteRepository.findBySemester(semester);
}
// Get notes by department
@GetMapping("/department")
public List<NotePyq> getNotesByDepartment(@RequestParam String dept) {
syncUploads();
return noteRepository.findByDepartment(dept);
}