feat(notes-pyqs): implement dynamic notes scanning, subject dropdown, and redesign UI
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Web MVC starter for building REST APIs -->
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
backend/uploads/sem1/Matrices and Calculus/syllabus/syllabus.pdf
Normal file
BIN
backend/uploads/sem1/Matrices and Calculus/syllabus/syllabus.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user