11import { appendEntry , getEntries , initAudit , type AuditEvent } from '@shieldcv/audit' ;
2- import type { ApplicationRecord } from '@shieldcv/compliance' ;
2+ import type { ApplicationRecord , TrackerApplicationRecord } from '@shieldcv/compliance' ;
33import { createBlankResume , normalizeResumeDocument , type ResumeDocument } from '@shieldcv/resume' ;
44import { EncryptedStore } from '@shieldcv/storage' ;
55import { writable } from 'svelte/store' ;
@@ -8,6 +8,7 @@ import { ATTACK_MODE_AUDIT_DATABASE_NAME, ATTACK_MODE_AUDIT_FLAG } from '$lib/de
88const DATABASE_NAME = 'shieldcv-local-vault' ;
99const RESUME_NAMESPACE = 'resume' ;
1010const GDPR_APPLICATION_NAMESPACE = 'gdpr-applications' ;
11+ const TRACKER_APPLICATION_NAMESPACE = 'tracker' ;
1112export const MAX_RESUME_SIZE = 500_000 ;
1213export type VaultLockReason = 'manual' | 'inactivity' ;
1314
@@ -71,6 +72,53 @@ function fireAndForgetAudit(event: AuditEvent, details: string): void {
7172 } ) ;
7273}
7374
75+ function normalizeTrackerApplication ( record : ApplicationRecord | TrackerApplicationRecord ) : TrackerApplicationRecord {
76+ return {
77+ ...record ,
78+ positionTitle :
79+ 'positionTitle' in record && typeof record . positionTitle === 'string' ? record . positionTitle : '' ,
80+ status :
81+ 'status' in record &&
82+ typeof record . status === 'string' &&
83+ [ 'applied' , 'screening' , 'interview' , 'offer' , 'rejected' , 'withdrawn' ] . includes ( record . status )
84+ ? record . status
85+ : 'applied' ,
86+ } ;
87+ }
88+
89+ async function migrateLegacyGdprApplications ( store : EncryptedStore ) : Promise < void > {
90+ const trackerIds = await store . list ( TRACKER_APPLICATION_NAMESPACE ) ;
91+ const legacyIds = await store . list ( GDPR_APPLICATION_NAMESPACE ) ;
92+
93+ if ( legacyIds . length === 0 ) {
94+ return ;
95+ }
96+
97+ const trackerIdSet = new Set ( trackerIds ) ;
98+
99+ await Promise . all (
100+ legacyIds . map ( async ( id ) => {
101+ if ( trackerIdSet . has ( id ) ) {
102+ await store . delete ( GDPR_APPLICATION_NAMESPACE , id ) ;
103+ return ;
104+ }
105+
106+ const legacyRecord = await store . get < ApplicationRecord > ( GDPR_APPLICATION_NAMESPACE , id ) ;
107+
108+ if ( ! legacyRecord ) {
109+ return ;
110+ }
111+
112+ await store . put (
113+ TRACKER_APPLICATION_NAMESPACE ,
114+ legacyRecord . id ,
115+ normalizeTrackerApplication ( legacyRecord ) ,
116+ ) ;
117+ await store . delete ( GDPR_APPLICATION_NAMESPACE , id ) ;
118+ } ) ,
119+ ) ;
120+ }
121+
74122export async function unlockVault ( passphrase : string ) : Promise < void > {
75123 vaultStatus . set ( 'unlocking' ) ;
76124
@@ -164,27 +212,83 @@ export async function deleteResume(id: string): Promise<void> {
164212 fireAndForgetAudit ( 'resume_deleted' , `Deleted resume ${ id } ` ) ;
165213}
166214
167- export async function listGdprApplications ( ) : Promise < ApplicationRecord [ ] > {
215+ export async function listGdprApplications ( ) : Promise < TrackerApplicationRecord [ ] > {
216+ const records = await listTrackerApplications ( ) ;
217+ return records ;
218+ }
219+
220+ export async function saveGdprApplication ( record : ApplicationRecord ) : Promise < ApplicationRecord > {
221+ return saveTrackerApplication ( normalizeTrackerApplication ( record ) ) ;
222+ }
223+
224+ export async function deleteGdprApplication ( id : string ) : Promise < void > {
225+ await deleteTrackerApplication ( id ) ;
226+ }
227+
228+ export async function listTrackerApplications ( ) : Promise < TrackerApplicationRecord [ ] > {
168229 const store = await resumeStore ( ) ;
169- const ids = await store . list ( GDPR_APPLICATION_NAMESPACE ) ;
230+ await migrateLegacyGdprApplications ( store ) ;
231+ const ids = await store . list ( TRACKER_APPLICATION_NAMESPACE ) ;
170232 const records = await Promise . all (
171- ids . map ( ( id ) => store . get < ApplicationRecord > ( GDPR_APPLICATION_NAMESPACE , id ) ) ,
233+ ids . map ( ( id ) => store . get < TrackerApplicationRecord > ( TRACKER_APPLICATION_NAMESPACE , id ) ) ,
172234 ) ;
173235
174236 return records
175- . filter ( ( record ) : record is ApplicationRecord => record !== undefined )
237+ . filter ( ( record ) : record is TrackerApplicationRecord => record !== undefined )
238+ . map ( ( record ) => normalizeTrackerApplication ( record ) )
176239 . sort ( ( left , right ) => right . dateApplied . localeCompare ( left . dateApplied ) ) ;
177240}
178241
179- export async function saveGdprApplication ( record : ApplicationRecord ) : Promise < ApplicationRecord > {
242+ export async function saveTrackerApplication (
243+ record : TrackerApplicationRecord ,
244+ ) : Promise < TrackerApplicationRecord > {
180245 const store = await resumeStore ( ) ;
181- await store . put ( GDPR_APPLICATION_NAMESPACE , record . id , record ) ;
246+ await migrateLegacyGdprApplications ( store ) ;
247+ await store . put ( TRACKER_APPLICATION_NAMESPACE , record . id , normalizeTrackerApplication ( record ) ) ;
182248 return record ;
183249}
184250
185- export async function deleteGdprApplication ( id : string ) : Promise < void > {
251+ export async function deleteTrackerApplication ( id : string ) : Promise < void > {
252+ const store = await resumeStore ( ) ;
253+ await migrateLegacyGdprApplications ( store ) ;
254+ await store . delete ( TRACKER_APPLICATION_NAMESPACE , id ) ;
255+ }
256+
257+ export async function createTrackerApplication (
258+ record : TrackerApplicationRecord ,
259+ ) : Promise < TrackerApplicationRecord > {
260+ const normalized = normalizeTrackerApplication ( record ) ;
261+ await saveTrackerApplication ( normalized ) ;
262+ fireAndForgetAudit (
263+ 'application_created' ,
264+ `Created application for ${ normalized . company } - ${ normalized . positionTitle } on ${ normalized . platform } .` ,
265+ ) ;
266+ return normalized ;
267+ }
268+
269+ export async function updateTrackerApplication (
270+ record : TrackerApplicationRecord ,
271+ ) : Promise < TrackerApplicationRecord > {
272+ const normalized = normalizeTrackerApplication ( record ) ;
273+ await saveTrackerApplication ( normalized ) ;
274+ fireAndForgetAudit (
275+ 'application_updated' ,
276+ `Updated application for ${ normalized . company } - ${ normalized . positionTitle } on ${ normalized . platform } .` ,
277+ ) ;
278+ return normalized ;
279+ }
280+
281+ export async function removeTrackerApplication ( id : string ) : Promise < void > {
186282 const store = await resumeStore ( ) ;
187- await store . delete ( GDPR_APPLICATION_NAMESPACE , id ) ;
283+ await migrateLegacyGdprApplications ( store ) ;
284+ const existing = await store . get < TrackerApplicationRecord > ( TRACKER_APPLICATION_NAMESPACE , id ) ;
285+ await store . delete ( TRACKER_APPLICATION_NAMESPACE , id ) ;
286+ fireAndForgetAudit (
287+ 'application_deleted' ,
288+ existing
289+ ? `Deleted application for ${ existing . company } - ${ existing . positionTitle } on ${ existing . platform } .`
290+ : `Deleted application ${ id } .` ,
291+ ) ;
188292}
189293
190294function clearBrowserStorage ( ) : void {
0 commit comments