Skip to content

Admin backend - #39

Closed
JinethBosilu wants to merge 3 commits into
mainfrom
admin-backend
Closed

Admin backend#39
JinethBosilu wants to merge 3 commits into
mainfrom
admin-backend

Conversation

@JinethBosilu

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings March 13, 2026 04:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements admin backend functionality for the CrimeLink Analyzer, replacing placeholder/TODO backup and restore endpoints with real implementations, and adding system settings management.

Changes:

  • Added BackupService that exports database tables as SQL INSERT statements and restores from backup files, with BackupMetadata entity for tracking backups
  • Added SystemSettingsService with key-value settings stored in DB, including validation rules and defaults
  • Secured backup/restore/settings endpoints to Admin-only via both SecurityConfig URL rules and @PreAuthorize annotations

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
BackupService.java New service for JDBC-based database backup/restore
SystemSettingsService.java New service for managing system settings with defaults and validation
AdminController.java Added endpoints for backups list, settings CRUD; wired real backup/restore logic
SecurityConfig.java Added Admin-only URL security rules for new endpoints
BackupMetadata.java New entity for backup tracking
SystemSetting.java New entity for key-value settings
BackupMetadataRepository.java New JPA repository for backup metadata
SystemSettingRepository.java New JPA repository for system settings
application.properties Added backup directory configuration
.gitignore Added backups directory to gitignore

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

try (BufferedWriter writer = new BufferedWriter(new FileWriter(backupFile.toFile()))) {
writer.write("-- CrimeLink Analyzer Database Backup\n");
writer.write("-- Created: " + LocalDateTime.now() + "\n");
writer.write("-- Created by: " + userEmail + "\n");
Comment on lines 252 to 254
} catch (Exception e) {
return ResponseEntity.status(500).body(
Map.of("message", "Restore failed: " + e.getMessage())
Comment on lines +54 to +94
public Map<String, String> updateSettings(Map<String, String> incoming) {
for (Map.Entry<String, String> entry : incoming.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();

// Ignore unknown keys
if (!DEFAULTS.containsKey(key)) {
log.warn("Ignoring unknown setting key: {}", sanitizeForLog(key));
continue;
}

// Validate numeric value
int numericValue;
try {
numericValue = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Setting '" + key + "' must be a valid integer, got: " + value);
}

int[] range = VALIDATION_RULES.get(key);
if (range != null && (numericValue < range[0] || numericValue > range[1])) {
throw new IllegalArgumentException(
"Setting '" + key + "' must be between " + range[0] + " and " + range[1] +
", got: " + numericValue);
}

// Upsert
SystemSetting setting = settingRepo.findBySettingKey(key)
.orElseGet(() -> {
SystemSetting s = new SystemSetting();
s.setSettingKey(key);
return s;
});
setting.setSettingValue(value);
settingRepo.save(setting);
}

log.info("System settings updated: {}", sanitizeForLog(incoming.keySet().toString()));
return getAllSettings();
}
Comment on lines +116 to +124
String[] statements = sql.split(";");
int executed = 0;

for (String stmt : statements) {
String trimmed = stmt.trim();
// Skip empty lines and comments
if (trimmed.isEmpty() || trimmed.startsWith("--")) {
continue;
}
Comment on lines +105 to +143
public void restoreBackup(String filename, String userEmail) {
Path backupFile = resolveSafeBackupPath(filename);

if (!Files.exists(backupFile)) {
throw new IllegalArgumentException("Backup file not found: " + filename);
}

try {
String sql = Files.readString(backupFile);

// Split by semicolons and execute each statement
String[] statements = sql.split(";");
int executed = 0;

for (String stmt : statements) {
String trimmed = stmt.trim();
// Skip empty lines and comments
if (trimmed.isEmpty() || trimmed.startsWith("--")) {
continue;
}
try {
jdbcTemplate.execute(trimmed);
executed++;
} catch (Exception stmtEx) {
// Log and continue — some statements may fail on duplicates etc.
log.warn("Skipping failed statement: {}... Error: {}",
sanitizeForLog(trimmed.substring(0, Math.min(80, trimmed.length()))),
sanitizeForLog(stmtEx.getMessage()));
}
}

log.info("Database restored from {} by {} ({} statements executed)",
sanitizeForLog(filename), sanitizeForLog(userEmail), executed);

} catch (IOException e) {
log.error("Restore failed: {}", sanitizeForLog(e.getMessage()), e);
throw new RuntimeException("Restore failed: " + e.getMessage(), e);
}
}
Comment on lines +70 to +79
for (String table : tables) {
writer.write("\n-- ========================================\n");
writer.write("-- Table: " + table + "\n");
writer.write("-- ========================================\n\n");

// Export table data as INSERT statements
exportTableData(writer, table);
}

writer.write("\n-- Backup complete\n");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants