Compare commits

...

8 Commits

Author SHA1 Message Date
Shanmuga Krishnan S M
f368c83246 fix(skillrack): update PROFILE_URL to manageprofile.xhtml (fixing 404 error) and simplify auth failure check
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-28 09:48:50 +05:30
Shanmuga Krishnan S M
db5ac8e2ea fix(skillrack): update login to j_security_check endpoint, exact ion-icon medal and points regex extraction, and rename input label to SkillRack Login ID
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-28 09:44:56 +05:30
Shanmuga Krishnan S M
a19c36579e feat(skillrack): add SkillRack leaderboard with server-side login, AES-encrypted credential storage, Jsoup HTML scraping, daily auto-sync, and full frontend tab with registration modal
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-28 09:35:42 +05:30
Shanmuga Krishnan S M
6be263771d feat(admin): add dedicated role-based logins and scoped permissions for Community Q&A, Clubs & Centers, and GPA Curriculum 2026-08-24 15:22:35 +05:30
Shanmuga Krishnan S M
9275722e16 feat(admin): make telegram bot config and senior helper id management fully functional with live test ping and verification 2026-08-24 14:04:11 +05:30
Shanmuga Krishnan S M
6740400788 Merge remote-tracking branch 'github/main' 2026-08-24 12:22:38 +05:30
Devesh S
6d386273fb Merge branch 'main' of https://github.com/Amudieshwar-AG/Freshers-Hub 2026-08-24 11:47:28 +05:30
Devesh S
56c3849357 student leaderboard - certifications 2026-08-24 11:46:58 +05:30
14 changed files with 3463 additions and 739 deletions

View File

@@ -69,6 +69,13 @@
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>
</dependency> </dependency>
<!-- Jsoup HTML Parser for SkillRack profile scraping -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.18.1</version>
</dependency>
<!-- Spring Boot Starter Test --> <!-- Spring Boot Starter Test -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -46,6 +47,24 @@ public class AdminController {
@Value("${admin.transport.password:RIT@2026}") @Value("${admin.transport.password:RIT@2026}")
private String transportPassword; private String transportPassword;
@Value("${admin.community.username:Community}")
private String communityUsername;
@Value("${admin.community.password:RIT@2026}")
private String communityPassword;
@Value("${admin.clubs.username:Clubs}")
private String clubsUsername;
@Value("${admin.clubs.password:RIT@2026}")
private String clubsPassword;
@Value("${admin.curriculum.username:Curriculum}")
private String curriculumUsername;
@Value("${admin.curriculum.password:RIT@2026}")
private String curriculumPassword;
@Value("${admin.super.username:Admin}") @Value("${admin.super.username:Admin}")
private String superAdminUsername; private String superAdminUsername;
@@ -71,18 +90,62 @@ public class AdminController {
if (p != null) inputPass = p.toString().trim(); if (p != null) inputPass = p.toString().trim();
} }
boolean isStandardPass = "RIT@2026".equalsIgnoreCase(inputPass);
// 1. Check Transport Admin ("Transport" / "RIT@2026") // 1. Check Transport Admin ("Transport" / "RIT@2026")
if (transportUsername.equalsIgnoreCase(inputUser) && transportPassword.equals(inputPass)) { if ((transportUsername.equalsIgnoreCase(inputUser) && (transportPassword.equals(inputPass) || isStandardPass)) ||
"transportadmin".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true); response.put("success", true);
response.put("role", "ROLE_TRANSPORT"); response.put("role", "ROLE_TRANSPORT");
response.put("username", "Transport Admin"); response.put("username", "Transport Admin");
response.put("token", "TRANSPORT_SESSION_TOKEN_RIT_2026"); response.put("token", "TRANSPORT_SESSION_TOKEN_RIT_2026");
response.put("message", "Transport admin login successful"); response.put("message", "Transport fleet admin login successful");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} }
// 2. Check Super Admin ("Admin" / "RIT@2026" or "ritadmin" / "ritadmin2026") // 2. Check Community & Senior Q&A Admin ("Community" / "RIT@2026" or "QA")
if ((superAdminUsername.equalsIgnoreCase(inputUser) && superAdminPassword.equals(inputPass)) || if ((communityUsername.equalsIgnoreCase(inputUser) && (communityPassword.equals(inputPass) || isStandardPass)) ||
"qa".equalsIgnoreCase(inputUser) && isStandardPass ||
"communityadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
"seniorqa".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true);
response.put("role", "ROLE_COMMUNITY");
response.put("username", "Community & Q&A Admin");
response.put("token", "COMMUNITY_SESSION_TOKEN_RIT_2026");
response.put("message", "Community & Senior Q&A admin login successful");
return ResponseEntity.ok(response);
}
// 3. Check Clubs & Centers Admin ("Clubs" / "RIT@2026" or "Club")
if ((clubsUsername.equalsIgnoreCase(inputUser) && (clubsPassword.equals(inputPass) || isStandardPass)) ||
"club".equalsIgnoreCase(inputUser) && isStandardPass ||
"clubadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
"clubsadmin".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true);
response.put("role", "ROLE_CLUBS");
response.put("username", "Clubs & Centers Admin");
response.put("token", "CLUBS_SESSION_TOKEN_RIT_2026");
response.put("message", "Clubs & Centers admin login successful");
return ResponseEntity.ok(response);
}
// 4. Check GPA Curriculum Admin ("Curriculum" / "RIT@2026" or "GPA" or "Academics")
if ((curriculumUsername.equalsIgnoreCase(inputUser) && (curriculumPassword.equals(inputPass) || isStandardPass)) ||
"gpa".equalsIgnoreCase(inputUser) && isStandardPass ||
"gpaadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
"academics".equalsIgnoreCase(inputUser) && isStandardPass ||
"curriculumadmin".equalsIgnoreCase(inputUser) && isStandardPass) {
response.put("success", true);
response.put("role", "ROLE_CURRICULUM");
response.put("username", "GPA Curriculum Admin");
response.put("token", "CURRICULUM_SESSION_TOKEN_RIT_2026");
response.put("message", "GPA Curriculum admin login successful");
return ResponseEntity.ok(response);
}
// 5. Check Super Admin ("Admin" / "RIT@2026" or "ritadmin" / "ritadmin2026" or "superadmin")
if ((superAdminUsername.equalsIgnoreCase(inputUser) && (superAdminPassword.equals(inputPass) || isStandardPass)) ||
"superadmin".equalsIgnoreCase(inputUser) && isStandardPass ||
("ritadmin".equalsIgnoreCase(inputUser) && "ritadmin2026".equals(inputPass))) { ("ritadmin".equalsIgnoreCase(inputUser) && "ritadmin2026".equals(inputPass))) {
response.put("success", true); response.put("success", true);
response.put("role", "ROLE_SUPER_ADMIN"); response.put("role", "ROLE_SUPER_ADMIN");
@@ -93,7 +156,7 @@ public class AdminController {
} }
response.put("success", false); response.put("success", false);
response.put("message", "Invalid credentials. Use 'Transport' or 'Admin' with password 'RIT@2026'."); response.put("message", "Invalid credentials. Use 'Transport', 'Community', 'Clubs', 'Curriculum', or 'Admin' with password 'RIT@2026'.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
} catch (Exception e) { } catch (Exception e) {
@@ -257,15 +320,47 @@ public class AdminController {
if (Files.exists(path)) { if (Files.exists(path)) {
try { try {
byte[] bytes = Files.readAllBytes(path); byte[] bytes = Files.readAllBytes(path);
return objectMapper.readValue(bytes, Map.class); Map<String, Object> loaded = objectMapper.readValue(bytes, Map.class);
if (loaded != null) {
List<Object> rawChatIds = (List<Object>) loaded.get("helper_chat_ids");
List<Map<String, Object>> helpers = (List<Map<String, Object>>) loaded.get("seniorHelpers");
if (helpers == null && rawChatIds != null) {
helpers = new ArrayList<>();
for (Object id : rawChatIds) {
Map<String, Object> h = new HashMap<>();
h.put("chatId", Long.parseLong(id.toString()));
h.put("name", "Senior Responder");
helpers.add(h);
}
loaded.put("seniorHelpers", helpers);
} else if (helpers != null && rawChatIds == null) {
List<Long> ids = new ArrayList<>();
for (Map<String, Object> h : helpers) {
if (h.get("chatId") != null) {
try {
ids.add(Long.parseLong(h.get("chatId").toString()));
} catch (Exception ignored) {}
}
}
loaded.put("helper_chat_ids", ids);
}
return loaded;
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
Map<String, Object> defaultCfg = new HashMap<>(); Map<String, Object> defaultCfg = new HashMap<>();
defaultCfg.put("community_bot_token", ""); defaultCfg.put("community_bot_token", "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY");
defaultCfg.put("telegram_bot_token", ""); defaultCfg.put("telegram_bot_token", "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY");
defaultCfg.put("helper_chat_ids", new ArrayList<Long>(List.of(971749136L, 5567776672L, 1873240361L))); defaultCfg.put("helper_chat_ids", new ArrayList<Long>(List.of(1873240361L, 5567776672L, 8518850169L, 7238144438L)));
List<Map<String, Object>> defaultHelpers = new ArrayList<>();
defaultHelpers.add(Map.of("chatId", 1873240361L, "name", "Senior Mentor (CSE)"));
defaultHelpers.add(Map.of("chatId", 5567776672L, "name", "Senior Responder (ECE)"));
defaultHelpers.add(Map.of("chatId", 8518850169L, "name", "Senior Responder (IT)"));
defaultHelpers.add(Map.of("chatId", 7238144438L, "name", "Senior Responder (AIDS)"));
defaultCfg.put("seniorHelpers", defaultHelpers);
defaultCfg.put("spring_backend_url", "http://localhost:8085"); defaultCfg.put("spring_backend_url", "http://localhost:8085");
return defaultCfg; return defaultCfg;
} }
@@ -293,6 +388,25 @@ public class AdminController {
if (payload.containsKey("community_bot_token")) current.put("community_bot_token", payload.get("community_bot_token")); if (payload.containsKey("community_bot_token")) current.put("community_bot_token", payload.get("community_bot_token"));
if (payload.containsKey("telegram_bot_token")) current.put("telegram_bot_token", payload.get("telegram_bot_token")); if (payload.containsKey("telegram_bot_token")) current.put("telegram_bot_token", payload.get("telegram_bot_token"));
if (payload.containsKey("spring_backend_url")) current.put("spring_backend_url", payload.get("spring_backend_url")); if (payload.containsKey("spring_backend_url")) current.put("spring_backend_url", payload.get("spring_backend_url"));
if (payload.containsKey("bot_username")) current.put("bot_username", payload.get("bot_username"));
if (payload.containsKey("seniorHelpers")) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> helpers = (List<Map<String, Object>>) payload.get("seniorHelpers");
current.put("seniorHelpers", helpers);
List<Long> ids = new ArrayList<>();
if (helpers != null) {
for (Map<String, Object> h : helpers) {
if (h.get("chatId") != null) {
try {
ids.add(Long.parseLong(h.get("chatId").toString()));
} catch (Exception ignored) {}
}
}
}
current.put("helper_chat_ids", ids);
} else if (payload.containsKey("helper_chat_ids")) {
current.put("helper_chat_ids", payload.get("helper_chat_ids"));
}
saveTelegramConfigFile(current); saveTelegramConfigFile(current);
return ResponseEntity.ok(Map.of("success", true, "config", current)); return ResponseEntity.ok(Map.of("success", true, "config", current));
} }
@@ -300,6 +414,7 @@ public class AdminController {
@PostMapping("/telegram/helpers") @PostMapping("/telegram/helpers")
public ResponseEntity<?> addSeniorHelper(@RequestBody Map<String, Object> payload) { public ResponseEntity<?> addSeniorHelper(@RequestBody Map<String, Object> payload) {
Object chatIdObj = payload.get("chatId"); Object chatIdObj = payload.get("chatId");
String nameStr = payload.get("name") != null ? payload.get("name").toString().trim() : "Senior Responder";
if (chatIdObj == null) { if (chatIdObj == null) {
return ResponseEntity.badRequest().body(Map.of("error", "chatId is required")); return ResponseEntity.badRequest().body(Map.of("error", "chatId is required"));
} }
@@ -313,38 +428,142 @@ public class AdminController {
Map<String, Object> config = readTelegramConfigFile(); Map<String, Object> config = readTelegramConfigFile();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Object> rawHelpers = (List<Object>) config.getOrDefault("helper_chat_ids", new ArrayList<>()); List<Map<String, Object>> helpers = (List<Map<String, Object>>) config.getOrDefault("seniorHelpers", new ArrayList<>());
List<Long> helpers = new ArrayList<>(); if (helpers == null) helpers = new ArrayList<>();
for (Object o : rawHelpers) { List<Long> helperIds = new ArrayList<>();
helpers.add(Long.parseLong(o.toString()));
boolean found = false;
for (Map<String, Object> h : helpers) {
if (h.get("chatId") != null) {
Long cId = Long.parseLong(h.get("chatId").toString());
helperIds.add(cId);
if (cId.equals(chatId)) {
h.put("name", nameStr);
found = true;
}
}
} }
if (!helpers.contains(chatId)) { if (!found) {
helpers.add(chatId); Map<String, Object> newHelper = new HashMap<>();
config.put("helper_chat_ids", helpers); newHelper.put("chatId", chatId);
newHelper.put("name", nameStr);
helpers.add(newHelper);
helperIds.add(chatId);
}
config.put("seniorHelpers", helpers);
config.put("helper_chat_ids", helperIds);
saveTelegramConfigFile(config); saveTelegramConfigFile(config);
}
return ResponseEntity.ok(Map.of("success", true, "helpers", helpers)); return ResponseEntity.ok(Map.of("success", true, "helpers", helpers, "helper_chat_ids", helperIds));
} }
@DeleteMapping("/telegram/helpers/{chatId}") @DeleteMapping("/telegram/helpers/{chatId}")
public ResponseEntity<?> removeSeniorHelper(@PathVariable Long chatId) { public ResponseEntity<?> removeSeniorHelper(@PathVariable Long chatId) {
Map<String, Object> config = readTelegramConfigFile(); Map<String, Object> config = readTelegramConfigFile();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Object> rawHelpers = (List<Object>) config.getOrDefault("helper_chat_ids", new ArrayList<>()); List<Map<String, Object>> helpers = (List<Map<String, Object>>) config.getOrDefault("seniorHelpers", new ArrayList<>());
List<Long> helpers = new ArrayList<>(); if (helpers != null) {
for (Object o : rawHelpers) { helpers.removeIf(h -> h.get("chatId") != null && Long.parseLong(h.get("chatId").toString()) == chatId);
helpers.add(Long.parseLong(o.toString())); } else {
helpers = new ArrayList<>();
} }
boolean removed = helpers.removeIf(id -> id.equals(chatId)); List<Long> helperIds = new ArrayList<>();
if (removed) { for (Map<String, Object> h : helpers) {
config.put("helper_chat_ids", helpers); if (h.get("chatId") != null) {
try {
helperIds.add(Long.parseLong(h.get("chatId").toString()));
} catch (Exception ignored) {}
}
}
config.put("seniorHelpers", helpers);
config.put("helper_chat_ids", helperIds);
saveTelegramConfigFile(config); saveTelegramConfigFile(config);
return ResponseEntity.ok(Map.of("success", true, "helpers", helpers, "helper_chat_ids", helperIds));
} }
return ResponseEntity.ok(Map.of("success", true, "helpers", helpers)); @PostMapping("/telegram/test-bot")
public ResponseEntity<?> testTelegramBot(@RequestBody Map<String, Object> payload) {
String token = (String) payload.get("token");
if (token == null || token.trim().isEmpty()) {
Map<String, Object> cfg = readTelegramConfigFile();
token = (String) cfg.getOrDefault("community_bot_token", "");
}
if (token == null || token.trim().isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("ok", false, "description", "Bot token is required"));
}
try {
RestTemplate restTemplate = new RestTemplate();
String url = "https://api.telegram.org/bot" + token.trim() + "/getMe";
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
return ResponseEntity.ok(response.getBody());
} catch (Exception e) {
return ResponseEntity.ok(Map.of("ok", false, "description", e.getMessage()));
}
}
@PostMapping("/telegram/test-dispatch")
public ResponseEntity<?> testTelegramDispatch(@RequestBody Map<String, Object> payload) {
String token = (String) payload.get("token");
Object chatIdObj = payload.get("chatId");
String message = (String) payload.get("message");
if (token == null || token.trim().isEmpty()) {
Map<String, Object> cfg = readTelegramConfigFile();
token = (String) cfg.getOrDefault("community_bot_token", "");
}
if (token == null || token.trim().isEmpty() || chatIdObj == null) {
return ResponseEntity.badRequest().body(Map.of("ok", false, "description", "Bot token and chatId are required"));
}
if (message == null || message.trim().isEmpty()) {
message = "🔔 *RIT Nexus Admin Test Ping*\n\nThis is a verified test notification confirming your Telegram Chat ID is active and connected to the Q&A dispatch system!";
}
try {
RestTemplate restTemplate = new RestTemplate();
String url = "https://api.telegram.org/bot" + token.trim() + "/sendMessage";
Map<String, Object> body = new HashMap<>();
body.put("chat_id", chatIdObj);
body.put("text", message);
body.put("parse_mode", "Markdown");
ResponseEntity<Map> response = restTemplate.postForEntity(url, body, Map.class);
return ResponseEntity.ok(response.getBody());
} catch (Exception e) {
return ResponseEntity.ok(Map.of("ok", false, "description", e.getMessage()));
}
}
@PostMapping("/telegram/test-broadcast")
public ResponseEntity<?> testTelegramBroadcast(@RequestBody Map<String, Object> payload) {
Map<String, Object> cfg = readTelegramConfigFile();
String token = (String) payload.getOrDefault("token", cfg.get("community_bot_token"));
String message = (String) payload.getOrDefault("message", "📢 *RIT Nexus Q&A Dispatch Network Test*\n\nAll senior responders are currently being pinged to verify real-time dispatch connectivity.");
@SuppressWarnings("unchecked")
List<Object> rawHelpers = (List<Object>) cfg.getOrDefault("helper_chat_ids", new ArrayList<>());
int delivered = 0;
List<Map<String, Object>> results = new ArrayList<>();
RestTemplate restTemplate = new RestTemplate();
if (token != null && !token.trim().isEmpty() && rawHelpers != null) {
for (Object idObj : rawHelpers) {
try {
String url = "https://api.telegram.org/bot" + token.trim() + "/sendMessage";
Map<String, Object> body = new HashMap<>();
body.put("chat_id", idObj);
body.put("text", message);
body.put("parse_mode", "Markdown");
ResponseEntity<Map> res = restTemplate.postForEntity(url, body, Map.class);
results.add(Map.of("chatId", idObj, "status", "success", "response", res.getBody()));
delivered++;
} catch (Exception e) {
results.add(Map.of("chatId", idObj, "status", "failed", "error", e.getMessage()));
}
}
}
return ResponseEntity.ok(Map.of("success", true, "delivered", delivered, "total", rawHelpers != null ? rawHelpers.size() : 0, "details", results));
} }
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────

View File

@@ -0,0 +1,68 @@
package com.rit.portal.controller;
import com.rit.portal.entity.SkillrackProfile;
import com.rit.portal.service.SkillrackService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/skillrack")
@CrossOrigin(originPatterns = "*")
@RequiredArgsConstructor
public class SkillrackController {
private final SkillrackService skillrackService;
@GetMapping("/leaderboard")
public List<SkillrackProfile> getLeaderboard() {
return skillrackService.getAllRankedProfiles();
}
@PostMapping("/register")
public ResponseEntity<?> registerProfile(@RequestBody RegisterRequest request) {
if (request.getSkillrackEmail() == null || request.getSkillrackEmail().trim().isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "SkillRack email is required"));
}
if (request.getSkillrackPassword() == null || request.getSkillrackPassword().trim().isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "SkillRack password is required"));
}
if (request.getStudentName() == null || request.getStudentName().trim().isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "Student name is required"));
}
try {
SkillrackProfile profile = skillrackService.registerOrUpdateStudent(
request.getStudentName(),
request.getSkillrackEmail(),
request.getSkillrackPassword(),
request.getDepartment() != null ? request.getDepartment() : "CSE",
request.getYear() != null ? request.getYear() : "1st Year"
);
return ResponseEntity.ok(profile);
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
} catch (Exception e) {
return ResponseEntity.internalServerError().body(Map.of("error", "Failed to register profile: " + e.getMessage()));
}
}
@PostMapping("/sync")
public ResponseEntity<?> triggerSync() {
skillrackService.syncAllProfilesWithSpacing();
return ResponseEntity.ok(Map.of("message", "SkillRack background sync initiated with 5-second spacing."));
}
@Data
public static class RegisterRequest {
private String studentName;
private String skillrackEmail;
private String skillrackPassword;
private String department;
private String year;
}
}

View File

@@ -0,0 +1,78 @@
package com.rit.portal.entity;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "skillrack_profiles")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SkillrackProfile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "student_name", nullable = false)
private String studentName;
@Column(name = "skillrack_email", nullable = false, unique = true)
private String skillrackEmail;
@Column(name = "encrypted_password", nullable = false, length = 512)
private String encryptedPassword;
@Column(nullable = false)
private String department;
@Column(nullable = false)
private String year;
@Column(name = "total_points")
@Builder.Default
private Integer totalPoints = 0;
@Column(name = "code_test_solved")
@Builder.Default
private Integer codeTestSolved = 0;
@Column(name = "code_tutor_solved")
@Builder.Default
private Integer codeTutorSolved = 0;
@Column(name = "code_track_solved")
@Builder.Default
private Integer codeTrackSolved = 0;
@Column(name = "dc_solved")
@Builder.Default
private Integer dcSolved = 0;
@Column(name = "gold_medals")
@Builder.Default
private Integer goldMedals = 0;
@Column(name = "silver_medals")
@Builder.Default
private Integer silverMedals = 0;
@Column(name = "bronze_medals")
@Builder.Default
private Integer bronzeMedals = 0;
@Column(name = "last_updated")
private LocalDateTime lastUpdated;
@Column(name = "created_at")
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
if (createdAt == null) createdAt = LocalDateTime.now();
if (lastUpdated == null) lastUpdated = LocalDateTime.now();
}
}

View File

@@ -0,0 +1,15 @@
package com.rit.portal.repository;
import com.rit.portal.entity.SkillrackProfile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface SkillrackProfileRepository extends JpaRepository<SkillrackProfile, Integer> {
Optional<SkillrackProfile> findBySkillrackEmailIgnoreCase(String skillrackEmail);
List<SkillrackProfile> findAllByOrderByTotalPointsDesc();
boolean existsBySkillrackEmailIgnoreCase(String skillrackEmail);
}

View File

@@ -0,0 +1,330 @@
package com.rit.portal.service;
import com.rit.portal.entity.SkillrackProfile;
import com.rit.portal.repository.SkillrackProfileRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
@RequiredArgsConstructor
@Slf4j
public class SkillrackService {
private final SkillrackProfileRepository repository;
// AES-128 key (16 bytes) — in production, move to environment variable
private static final String AES_KEY = "RitSkillR@ck2026";
private static final String SKILLRACK_BASE = "https://www.skillrack.com";
private static final String LOGIN_URL = SKILLRACK_BASE + "/faces/ui/profile.xhtml";
private static final String PROFILE_URL = SKILLRACK_BASE + "/faces/candidate/manageprofile.xhtml";
// ─── Public API ───────────────────────────────────────────────────
public List<SkillrackProfile> getAllRankedProfiles() {
return repository.findAllByOrderByTotalPointsDesc();
}
public SkillrackProfile registerOrUpdateStudent(String studentName, String email, String password,
String department, String year) {
String cleanEmail = email.trim().toLowerCase();
SkillrackProfile profile = repository.findBySkillrackEmailIgnoreCase(cleanEmail)
.orElse(SkillrackProfile.builder()
.studentName(studentName.trim())
.skillrackEmail(cleanEmail)
.department(department.trim())
.year(year.trim())
.build());
profile.setStudentName(studentName.trim());
profile.setDepartment(department.trim());
profile.setYear(year.trim());
profile.setEncryptedPassword(encrypt(password));
boolean success = fetchAndUpdateProfile(profile, password);
if (!success) {
throw new IllegalArgumentException(
"Could not authenticate with SkillRack using '" + cleanEmail +
"'. Please verify your email and password are correct.");
}
return repository.save(profile);
}
public boolean fetchAndUpdateProfile(SkillrackProfile profile) {
String password = decrypt(profile.getEncryptedPassword());
if (password == null || password.isEmpty()) {
log.warn("Cannot decrypt password for SkillRack profile: {}", profile.getSkillrackEmail());
return false;
}
return fetchAndUpdateProfile(profile, password);
}
/**
* Core scraping method using SkillRack's container security:
* 1. GET https://www.skillrack.com/faces/ui/profile.xhtml to establish JSESSIONID session
* 2. POST to https://www.skillrack.com/faces/ui/j_security_check with j_username (Login ID) & j_password
* 3. GET https://www.skillrack.com/faces/candidate/profileview.xhtml with authenticated session & parse HTML
*/
public boolean fetchAndUpdateProfile(SkillrackProfile profile, String rawPassword) {
try {
log.info("Fetching SkillRack stats for Login ID: {}", profile.getSkillrackEmail());
String loginPageUrl = SKILLRACK_BASE + "/faces/ui/profile.xhtml";
String jSecurityCheckUrl = SKILLRACK_BASE + "/faces/ui/j_security_check";
// Step 1: GET login page to establish session cookie
Connection.Response pageRes = Jsoup.connect(loginPageUrl)
.method(Connection.Method.GET)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(15000)
.execute();
Map<String, String> cookies = new HashMap<>(pageRes.cookies());
// Step 2: POST login credentials to j_security_check
Map<String, String> formData = new LinkedHashMap<>();
formData.put("j_username", profile.getSkillrackEmail().trim());
formData.put("j_password", rawPassword);
Connection.Response loginRes = Jsoup.connect(jSecurityCheckUrl)
.method(Connection.Method.POST)
.cookies(cookies)
.data(formData)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Referer", loginPageUrl)
.followRedirects(true)
.timeout(15000)
.execute();
cookies.putAll(loginRes.cookies());
// Step 3: Fetch candidate profile page with authenticated session
Connection.Response profileRes = Jsoup.connect(PROFILE_URL)
.method(Connection.Method.GET)
.cookies(cookies)
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.followRedirects(true)
.timeout(15000)
.execute();
cookies.putAll(profileRes.cookies());
Document profileDoc = profileRes.parse();
String bodyText = profileDoc.body().text();
// Check if login failed (still on login page containing j_security_check form)
if (bodyText.contains("j_security_check") || bodyText.contains("Invalid User ID")) {
log.warn("SkillRack authentication failed for: {}", profile.getSkillrackEmail());
return false;
}
// Step 4: Parse profile stats from the HTML
boolean parsed = parseProfileStats(profileDoc, profile);
if (!parsed) {
// Fallback: Try to extract from raw text with regex patterns
parsed = parseFromRawText(bodyText, profile);
}
if (parsed) {
profile.setLastUpdated(LocalDateTime.now());
log.info("Successfully scraped SkillRack for {}: Points={}, CT={}, Tutor={}, Track={}, DC={}, Gold={}, Silver={}, Bronze={}",
profile.getSkillrackEmail(), profile.getTotalPoints(),
profile.getCodeTestSolved(), profile.getCodeTutorSolved(),
profile.getCodeTrackSolved(), profile.getDcSolved(),
profile.getGoldMedals(), profile.getSilverMedals(), profile.getBronzeMedals());
return true;
}
log.warn("Could not parse SkillRack profile data for: {}", profile.getSkillrackEmail());
return false;
} catch (Exception e) {
log.error("Failed to fetch SkillRack data for {}: {}", profile.getSkillrackEmail(), e.getMessage());
return false;
}
}
// ─── HTML Parsing ─────────────────────────────────────────────────
private boolean parseProfileStats(Document doc, SkillrackProfile profile) {
String html = doc.html();
boolean foundAnything = false;
// 1. Extract Real Name from .ui-chip-text if available
Element chipEl = doc.selectFirst(".ui-chip-text");
if (chipEl != null) {
String chipText = chipEl.text().trim();
if (chipText.contains("-")) {
String[] parts = chipText.split("-");
if (parts.length >= 1 && !parts[0].trim().isEmpty()) {
profile.setStudentName(parts[0].trim());
}
}
}
// 2. Gold Medals (orange medal icon)
Matcher goldM = Pattern.compile("ion-md-medal\"\\s+style=\"color:orange\"></i>\\s*(\\d+)").matcher(html);
if (goldM.find()) {
profile.setGoldMedals(Integer.parseInt(goldM.group(1)));
foundAnything = true;
}
// 3. Silver Medals (grey medal icon)
Matcher silverM = Pattern.compile("ion-md-medal\"\\s+style=\"color:grey\"></i>\\s*(\\d+)").matcher(html);
if (silverM.find()) {
profile.setSilverMedals(Integer.parseInt(silverM.group(1)));
foundAnything = true;
}
// 4. Bronze Medals (brown medal icon)
Matcher bronzeM = Pattern.compile("ion-md-medal\"\\s+style=\"color:brown\"></i>\\s*(\\d+)").matcher(html);
if (bronzeM.find()) {
profile.setBronzeMedals(Integer.parseInt(bronzeM.group(1)));
foundAnything = true;
}
// 5. Total Points / Score (brown flag icon)
Matcher pointsM = Pattern.compile("ion-md-flag\"\\s+style=\"color:brown\"></i>\\s*(\\d+)").matcher(html);
if (pointsM.find()) {
profile.setTotalPoints(Integer.parseInt(pointsM.group(1)));
foundAnything = true;
}
return foundAnything;
}
private boolean parseFromRawText(String text, SkillrackProfile profile) {
boolean found = false;
// Pattern: "Code Test 123" or "CodeTest: 123" or "Code Test Solved: 123"
found |= extractAndSet(text, "(?i)code\\s*test[^\\d]*(\\d+)", val -> profile.setCodeTestSolved(val));
found |= extractAndSet(text, "(?i)code\\s*tutor[^\\d]*(\\d+)", val -> profile.setCodeTutorSolved(val));
found |= extractAndSet(text, "(?i)code\\s*track[^\\d]*(\\d+)", val -> profile.setCodeTrackSolved(val));
found |= extractAndSet(text, "(?i)daily\\s*challenge[^\\d]*(\\d+)", val -> profile.setDcSolved(val));
found |= extractAndSet(text, "(?i)(?:dc|DC)[^\\d]*(\\d+)", val -> {
if (profile.getDcSolved() == 0) profile.setDcSolved(val);
});
found |= extractAndSet(text, "(?i)gold[^\\d]*(\\d+)", val -> profile.setGoldMedals(val));
found |= extractAndSet(text, "(?i)silver[^\\d]*(\\d+)", val -> profile.setSilverMedals(val));
found |= extractAndSet(text, "(?i)bronze[^\\d]*(\\d+)", val -> profile.setBronzeMedals(val));
found |= extractAndSet(text, "(?i)total\\s*(?:point|score)s?[^\\d]*(\\d+)", val -> profile.setTotalPoints(val));
// If total points not found, compute from components
if (profile.getTotalPoints() == 0 && found) {
int computed = profile.getCodeTestSolved() + profile.getCodeTutorSolved()
+ profile.getCodeTrackSolved() + profile.getDcSolved();
if (computed > 0) {
profile.setTotalPoints(computed);
}
}
return found;
}
private boolean extractAndSet(String text, String regex, java.util.function.IntConsumer setter) {
Matcher m = Pattern.compile(regex).matcher(text);
if (m.find()) {
int val = parseIntSafe(m.group(1));
if (val > 0) {
setter.accept(val);
return true;
}
}
return false;
}
private int parseIntSafe(String s) {
try {
return Integer.parseInt(s.replaceAll("[^\\d]", ""));
} catch (NumberFormatException e) {
return 0;
}
}
// ─── AES Encryption Helpers ───────────────────────────────────────
private String encrypt(String plainText) {
try {
SecretKeySpec key = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
log.error("AES encryption failed: {}", e.getMessage());
throw new RuntimeException("Encryption failed", e);
}
}
private String decrypt(String encryptedBase64) {
try {
SecretKeySpec key = new SecretKeySpec(AES_KEY.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decoded = Base64.getDecoder().decode(encryptedBase64);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("AES decryption failed: {}", e.getMessage());
return null;
}
}
// ─── Scheduled & Manual Sync ──────────────────────────────────────
/**
* Automatic daily sync at 2:30 AM — spaced 5s apart to avoid rate limits
*/
@Scheduled(cron = "0 30 2 * * ?")
public void scheduledDailySync() {
log.info("Starting scheduled daily SkillRack sync...");
syncAllProfilesWithSpacing();
}
/**
* Manual or scheduled trigger to sync all profiles with 5-second delays.
*/
public void syncAllProfilesWithSpacing() {
List<SkillrackProfile> profiles = repository.findAll();
log.info("Queued {} SkillRack profiles for sync.", profiles.size());
new Thread(() -> {
for (int i = 0; i < profiles.size(); i++) {
SkillrackProfile p = profiles.get(i);
try {
boolean success = fetchAndUpdateProfile(p);
if (success) {
repository.save(p);
}
if (i < profiles.size() - 1) {
log.info("Waiting 5s before next SkillRack profile sync...");
Thread.sleep(5000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("SkillRack sync interrupted.");
break;
} catch (Exception e) {
log.error("Error syncing SkillRack profile {}: {}", p.getSkillrackEmail(), e.getMessage());
}
}
log.info("Finished SkillRack sync for all profiles.");
}).start();
}
}

View File

@@ -102,8 +102,19 @@ function App() {
path="/leetcode" path="/leetcode"
element={ element={
<ProtectedRoute <ProtectedRoute
title="RIT LeetCode Leaderboard" title="RIT Student Leaderboard"
description="Track campus competitive programming rankings and see top RIT coders." description="Track campus competitive programming rankings (LeetCode & SkillRack) and verified skill certifications."
>
<LeetcodeLeaderboard />
</ProtectedRoute>
}
/>
<Route
path="/student-leaderboard"
element={
<ProtectedRoute
title="RIT Student Leaderboard"
description="Track campus competitive programming rankings (LeetCode & SkillRack) and verified skill certifications."
> >
<LeetcodeLeaderboard /> <LeetcodeLeaderboard />
</ProtectedRoute> </ProtectedRoute>

View File

@@ -10,7 +10,7 @@ const exploreLinks = [
{ label: 'Student Toolkit', path: '/toolkit', icon: Wrench }, { label: 'Student Toolkit', path: '/toolkit', icon: Wrench },
{ label: 'RIT Chatbot', path: '/ai-assistant', icon: Bot }, { label: 'RIT Chatbot', path: '/ai-assistant', icon: Bot },
{ label: 'Faculty Directory', path: '/faculty', icon: UserCheck }, { label: 'Faculty Directory', path: '/faculty', icon: UserCheck },
{ label: 'LeetCode Arena', path: '/leetcode', icon: Trophy }, { label: 'Student Leaderboard', path: '/student-leaderboard', icon: Trophy },
]; ];
const campusLinks = [ const campusLinks = [

View File

@@ -49,7 +49,7 @@ const MORE_LINKS: NavItem[] = [
{ label: 'Campus Map', path: '/campus', icon: Map }, { label: 'Campus Map', path: '/campus', icon: Map },
{ label: 'Clubs & Centers', path: '/events', icon: Users }, { label: 'Clubs & Centers', path: '/events', icon: Users },
{ label: 'Dev Collab', path: '/collab', icon: Code2 }, { label: 'Dev Collab', path: '/collab', icon: Code2 },
{ label: 'LeetCode', path: '/leetcode', icon: Trophy }, { label: 'Student Leaderboard', path: '/student-leaderboard', icon: Trophy },
{ label: 'Student Gate Pass', path: '/notes?portal=gatepass', icon: Ticket }, { label: 'Student Gate Pass', path: '/notes?portal=gatepass', icon: Ticket },
{ label: 'Student Proficiency', path: '/notes?portal=proficiency', icon: Award }, { label: 'Student Proficiency', path: '/notes?portal=proficiency', icon: Award },
]; ];

View File

@@ -12,7 +12,7 @@ export const NAV_LINKS = [
{ label: 'Clubs & Centers', path: '/events' }, { label: 'Clubs & Centers', path: '/events' },
{ label: 'Community', path: '/community' }, { label: 'Community', path: '/community' },
{ label: 'Dev Collab', path: '/collab' }, { label: 'Dev Collab', path: '/collab' },
{ label: 'LeetCode', path: '/leetcode' }, { label: 'Student Leaderboard', path: '/student-leaderboard' },
]; ];
// ─── Stats ──────────────────────────────────────────────────────────────────── // ─── Stats ────────────────────────────────────────────────────────────────────
@@ -110,9 +110,9 @@ export const FEATURES: Feature[] = [
{ {
id: 'leetcode', id: 'leetcode',
icon: 'Trophy', icon: 'Trophy',
title: 'LeetCode Arena', title: 'Student Leaderboard',
description: 'Track campus competitive programming rankings and see top RIT coders.', description: 'Track campus competitive programming rankings (LeetCode & SkillRack) and verified skill certifications.',
path: '/leetcode', path: '/student-leaderboard',
color: '#F59E0B', color: '#F59E0B',
bgColor: '#FFFBEB', bgColor: '#FFFBEB',
}, },

View File

@@ -14,7 +14,7 @@ export interface AuthUser {
name: string; name: string;
pictureUrl?: string; pictureUrl?: string;
verifiedStudent: boolean; verifiedStudent: boolean;
role?: 'ROLE_STUDENT' | 'ROLE_TRANSPORT' | 'ROLE_SUPER_ADMIN' | 'ROLE_USER'; role?: 'ROLE_STUDENT' | 'ROLE_TRANSPORT' | 'ROLE_COMMUNITY' | 'ROLE_CLUBS' | 'ROLE_CURRICULUM' | 'ROLE_SUPER_ADMIN' | 'ROLE_USER';
regNumber?: string; regNumber?: string;
department?: string; department?: string;
} }
@@ -32,6 +32,9 @@ interface AuthContextType {
isVerifiedStudent: boolean; isVerifiedStudent: boolean;
isAdmin: boolean; isAdmin: boolean;
isTransportAdmin: boolean; isTransportAdmin: boolean;
isCommunityAdmin: boolean;
isClubsAdmin: boolean;
isCurriculumAdmin: boolean;
isLoading: boolean; isLoading: boolean;
isAuthModalOpen: boolean; isAuthModalOpen: boolean;
loginWithGoogle: () => void; loginWithGoogle: () => void;
@@ -59,6 +62,9 @@ const AuthContext = createContext<AuthContextType>({
isVerifiedStudent: false, isVerifiedStudent: false,
isAdmin: false, isAdmin: false,
isTransportAdmin: false, isTransportAdmin: false,
isCommunityAdmin: false,
isClubsAdmin: false,
isCurriculumAdmin: false,
isLoading: true, isLoading: true,
isAuthModalOpen: false, isAuthModalOpen: false,
loginWithGoogle: () => {}, loginWithGoogle: () => {},
@@ -200,9 +206,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { success: false, message: 'Please enter both username/register number and password.' }; return { success: false, message: 'Please enter both username/register number and password.' };
} }
const isAdminUsername = ['admin', 'transport', 'ritadmin', 'superadmin'].includes(cleanedUser.toLowerCase()); const isAdminUsername = [
'admin', 'transport', 'transportadmin',
'community', 'communityadmin', 'qa', 'seniorqa',
'clubs', 'club', 'clubsadmin',
'curriculum', 'curriculumadmin', 'gpa', 'gpaadmin', 'academics',
'ritadmin', 'superadmin'
].includes(cleanedUser.toLowerCase());
// 1. First, check if credentials match Admin / Transport (only for admin accounts) // 1. First, check if credentials match Admin / Role Endpoints
if (isAdminUsername) { if (isAdminUsername) {
try { try {
const adminRes = await fetch(getBackendUrl('/api/admin/login'), { const adminRes = await fetch(getBackendUrl('/api/admin/login'), {
@@ -221,7 +233,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const adminUserObj: AuthUser = { const adminUserObj: AuthUser = {
id: 999999, id: 999999,
name: data.username, name: data.username,
email: data.role === 'ROLE_TRANSPORT' ? 'transport@ritchennai.edu.in' : 'admin@ritchennai.edu.in', email: `${cleanedUser.toLowerCase()}@ritchennai.edu.in`,
verifiedStudent: false, verifiedStudent: false,
role: data.role, role: data.role,
}; };
@@ -241,11 +253,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
} }
} }
// Direct local check for Admin credentials fallback const isStdPass = cleanedPass === 'RIT@2026' || cleanedPass === 'rit@2026';
if (
cleanedUser.toLowerCase() === 'transport' && // Direct local check for Admin credentials fallbacks
cleanedPass === 'RIT@2026' if (['transport', 'transportadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) {
) {
const role = 'ROLE_TRANSPORT'; const role = 'ROLE_TRANSPORT';
localStorage.setItem(ADMIN_TOKEN_KEY, 'TRANSPORT_SESSION_TOKEN_RIT_2026'); localStorage.setItem(ADMIN_TOKEN_KEY, 'TRANSPORT_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role); localStorage.setItem(ADMIN_ROLE_KEY, role);
@@ -261,9 +272,57 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { success: true, role, redirectTo: '/admin' }; return { success: true, role, redirectTo: '/admin' };
} }
if (['community', 'qa', 'communityadmin', 'seniorqa'].includes(cleanedUser.toLowerCase()) && isStdPass) {
const role = 'ROLE_COMMUNITY';
localStorage.setItem(ADMIN_TOKEN_KEY, 'COMMUNITY_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'Community & Q&A Admin');
setUser({
id: 999999,
name: 'Community & Q&A Admin',
email: 'community@ritchennai.edu.in',
verifiedStudent: false,
role,
});
setIsAuthModalOpen(false);
return { success: true, role, redirectTo: '/admin' };
}
if (['clubs', 'club', 'clubsadmin', 'clubadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) {
const role = 'ROLE_CLUBS';
localStorage.setItem(ADMIN_TOKEN_KEY, 'CLUBS_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'Clubs & Centers Admin');
setUser({
id: 999999,
name: 'Clubs & Centers Admin',
email: 'clubs@ritchennai.edu.in',
verifiedStudent: false,
role,
});
setIsAuthModalOpen(false);
return { success: true, role, redirectTo: '/admin' };
}
if (['curriculum', 'gpa', 'academics', 'gpaadmin', 'curriculumadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) {
const role = 'ROLE_CURRICULUM';
localStorage.setItem(ADMIN_TOKEN_KEY, 'CURRICULUM_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'GPA Curriculum Admin');
setUser({
id: 999999,
name: 'GPA Curriculum Admin',
email: 'curriculum@ritchennai.edu.in',
verifiedStudent: false,
role,
});
setIsAuthModalOpen(false);
return { success: true, role, redirectTo: '/admin' };
}
if ( if (
(cleanedUser.toLowerCase() === 'admin' && (cleanedPass === 'RIT@2026' || cleanedPass === 'rit@2026')) || (['admin', 'superadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) ||
(cleanedUser.toLowerCase() === 'ritadmin' && cleanedPass === 'ritadmin2026') (cleanedUser.toLowerCase() === 'ritadmin' && (cleanedPass === 'ritadmin2026' || isStdPass))
) { ) {
const role = 'ROLE_SUPER_ADMIN'; const role = 'ROLE_SUPER_ADMIN';
localStorage.setItem(ADMIN_TOKEN_KEY, 'ADMIN_SESSION_TOKEN_RIT_2026'); localStorage.setItem(ADMIN_TOKEN_KEY, 'ADMIN_SESSION_TOKEN_RIT_2026');
@@ -299,23 +358,21 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return { return {
success: true, success: true,
role: 'ROLE_STUDENT', role: 'ROLE_STUDENT',
redirectTo: '/timetable', redirectTo: '/dashboard',
message: 'Student login successful', message: `Welcome ${imsResult.student.name}!`,
}; };
} else if (!imsResult.success && imsResult.message) { } else {
return { return {
success: false, success: false,
message: imsResult.message, message: imsResult.message || 'Authentication failed. Please check your credentials.',
}; };
} }
} catch { } catch (err: any) {
// Fallback
}
return { return {
success: false, success: false,
message: 'Invalid credentials. Enter your Register Number or Admin credentials (Password: rit@2026 / RIT@2026).', message: err.message || 'Network error while attempting login. Please try again.',
}; };
}
}, []); }, []);
const logout = useCallback(() => { const logout = useCallback(() => {
@@ -333,8 +390,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setIsAuthModalOpen(false); setIsAuthModalOpen(false);
}, [user]); }, [user]);
const isAdmin = user?.role === 'ROLE_SUPER_ADMIN' || user?.role === 'ROLE_TRANSPORT'; const isSuperAdmin = user?.role === 'ROLE_SUPER_ADMIN';
const isTransportAdmin = user?.role === 'ROLE_TRANSPORT'; const isTransportAdmin = user?.role === 'ROLE_TRANSPORT';
const isCommunityAdmin = user?.role === 'ROLE_COMMUNITY';
const isClubsAdmin = user?.role === 'ROLE_CLUBS';
const isCurriculumAdmin = user?.role === 'ROLE_CURRICULUM';
const isAdmin = isSuperAdmin || isTransportAdmin || isCommunityAdmin || isClubsAdmin || isCurriculumAdmin;
return ( return (
<AuthContext.Provider <AuthContext.Provider
@@ -344,6 +405,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
isVerifiedStudent: !!user?.verifiedStudent, isVerifiedStudent: !!user?.verifiedStudent,
isAdmin, isAdmin,
isTransportAdmin, isTransportAdmin,
isCommunityAdmin,
isClubsAdmin,
isCurriculumAdmin,
isLoading, isLoading,
isAuthModalOpen, isAuthModalOpen,
loginWithGoogle, loginWithGoogle,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,30 @@
{ {
"community_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY", "community_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY",
"telegram_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY", "telegram_bot_token": "8973721012:AAG37F4Q4q584m_2aS8rT6qSWuA-WuHRGMY",
"helper_chat_ids" : [ 1873240361, 5567776672, 8518850169, 7238144438 ], "bot_username": "FreshersCommunityBot",
"helper_chat_ids": [
1873240361,
5567776672,
8518850169,
7238144438
],
"seniorHelpers": [
{
"chatId": 1873240361,
"name": "Senior Mentor (CSE)"
},
{
"chatId": 5567776672,
"name": "Senior Responder (ECE)"
},
{
"chatId": 8518850169,
"name": "Senior Responder (IT)"
},
{
"chatId": 7238144438,
"name": "Senior Responder (AIDS)"
}
],
"spring_backend_url": "http://localhost:8085" "spring_backend_url": "http://localhost:8085"
} }