mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-22 14:10:10 +00:00
Add reported-issues, Goodreads sync & notifs
Introduce user-reported-issues and Goodreads shelf sync features and wire them into notifications. Adds Prisma migrations and schema changes (ReportedIssue, GoodreadsShelf, GoodreadsBookMapping), API endpoints for reporting (POST /audiobooks/[asin]/report-issue) and admin management (list, resolve/dismiss, replace), and an admin UI section to view/dismiss/replace reported issues. Adds a new notification event (issue_reported) with updates to notification schemas, docs and provider handling, plus a notification-events constants file. Refactors request creation to use createRequestForUser service, adds a Goodreads sync processor/service/hooks/UI modals, a scrape-resilience util, and related tests and minor integration updates.
This commit is contained in:
@@ -130,7 +130,7 @@ export class NotificationService {
|
||||
|
||||
const encrypted = { ...config };
|
||||
for (const field of provider.sensitiveFields) {
|
||||
if (encrypted[field] && !this.isEncrypted(encrypted[field])) {
|
||||
if (encrypted[field] && !this.encryptionService.isEncryptedFormat(encrypted[field])) {
|
||||
encrypted[field] = this.encryptionService.encrypt(encrypted[field]);
|
||||
}
|
||||
}
|
||||
@@ -155,25 +155,66 @@ export class NotificationService {
|
||||
return masked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-encrypt any sensitive fields that were stored as plaintext due to
|
||||
* the isEncrypted() false-positive bug (URLs with exactly 2 colons).
|
||||
* Safe to call multiple times — skips already-encrypted values.
|
||||
*/
|
||||
async reEncryptUnprotectedBackends(): Promise<number> {
|
||||
let fixed = 0;
|
||||
|
||||
try {
|
||||
const backends = await prisma.notificationBackend.findMany();
|
||||
|
||||
for (const backend of backends) {
|
||||
const provider = getProvider(backend.type);
|
||||
if (!provider) continue;
|
||||
|
||||
const config = backend.config as any;
|
||||
let needsUpdate = false;
|
||||
const updatedConfig = { ...config };
|
||||
|
||||
for (const field of provider.sensitiveFields) {
|
||||
if (updatedConfig[field] && !this.encryptionService.isEncryptedFormat(updatedConfig[field])) {
|
||||
updatedConfig[field] = this.encryptionService.encrypt(updatedConfig[field]);
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
await prisma.notificationBackend.update({
|
||||
where: { id: backend.id },
|
||||
data: { config: updatedConfig },
|
||||
});
|
||||
fixed++;
|
||||
logger.info(`Re-encrypted plaintext sensitive fields for backend: ${backend.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (fixed > 0) {
|
||||
logger.warn(`Re-encrypted ${fixed} backend(s) with unprotected sensitive fields`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to re-encrypt backends', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
|
||||
return fixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt sensitive config values
|
||||
*/
|
||||
private decryptConfig(sensitiveFields: string[], config: any): any {
|
||||
const decrypted = { ...config };
|
||||
for (const field of sensitiveFields) {
|
||||
if (decrypted[field] && this.isEncrypted(decrypted[field])) {
|
||||
if (decrypted[field] && this.encryptionService.isEncryptedFormat(decrypted[field])) {
|
||||
decrypted[field] = this.encryptionService.decrypt(decrypted[field]);
|
||||
}
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is encrypted (has iv:authTag:data format)
|
||||
*/
|
||||
private isEncrypted(value: string): boolean {
|
||||
return value.includes(':') && value.split(':').length === 3;
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
|
||||
Reference in New Issue
Block a user