Convert storage layer to 100% pure local disk JSON database (.data.json)
This commit is contained in:
@@ -815,6 +815,42 @@ app.post("/api/jobs/import", (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// API 14: Local Disk Storage Persistence Engine (No Cloud / Firebase Required)
|
||||
const DATA_FILE = path.join(process.cwd(), ".data.json");
|
||||
import fs from "fs";
|
||||
|
||||
app.get("/api/local/data", (req, res) => {
|
||||
try {
|
||||
const key = String(req.query.key || "");
|
||||
if (!fs.existsSync(DATA_FILE)) {
|
||||
return res.json({});
|
||||
}
|
||||
const raw = fs.readFileSync(DATA_FILE, "utf-8");
|
||||
const store = JSON.parse(raw || "{}");
|
||||
res.json(store[key] || {});
|
||||
} catch (e: any) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/local/data", (req, res) => {
|
||||
try {
|
||||
const { key, items, profile } = req.body;
|
||||
let store: any = {};
|
||||
if (fs.existsSync(DATA_FILE)) {
|
||||
try { store = JSON.parse(fs.readFileSync(DATA_FILE, "utf-8") || "{}"); } catch (e) {}
|
||||
}
|
||||
if (key) {
|
||||
if (items !== undefined) store[key] = { items };
|
||||
if (profile !== undefined) store[key] = { profile };
|
||||
}
|
||||
fs.writeFileSync(DATA_FILE, JSON.stringify(store, null, 2));
|
||||
res.json({ success: true });
|
||||
} catch (e: any) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// API 10: Google Drive - List Job Application Artifacts
|
||||
app.get("/api/workspace/drive/files", async (req, res) => {
|
||||
try {
|
||||
|
||||
+82
-135
@@ -1,185 +1,132 @@
|
||||
import { initializeApp, getApps, getApp } from 'firebase/app';
|
||||
import {
|
||||
getFirestore,
|
||||
collection,
|
||||
doc,
|
||||
getDocs,
|
||||
getDoc,
|
||||
setDoc,
|
||||
updateDoc,
|
||||
deleteDoc,
|
||||
addDoc,
|
||||
query,
|
||||
orderBy
|
||||
} from 'firebase/firestore';
|
||||
import firebaseConfig from '../../firebase-applet-config.json';
|
||||
import { JobApplication, UserProfile, NotificationLog, CalendarInterviewEvent } from '../types';
|
||||
|
||||
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();
|
||||
export const db = getFirestore(app);
|
||||
export const isFirebaseConfigured = false;
|
||||
|
||||
const APPS_COLLECTION = 'applications';
|
||||
const PROFILE_DOC = 'user_profile';
|
||||
const NOTIFICATIONS_COLLECTION = 'notifications';
|
||||
const CALENDAR_COLLECTION = 'calendar_events';
|
||||
// 100% Pure Local Disk & LocalStorage Persistence System
|
||||
|
||||
export const isFirebaseConfigured = true;
|
||||
|
||||
// Firestore Helper Functions with LocalStorage Sync Fallback
|
||||
const STORAGE_KEYS = {
|
||||
APPLICATIONS: 'ai_job_portal_applications',
|
||||
USER_PROFILE: 'ai_job_portal_profile',
|
||||
NOTIFICATIONS: 'ai_job_portal_notifications',
|
||||
CALENDAR: 'ai_job_portal_calendar'
|
||||
};
|
||||
|
||||
export async function fetchApplications(): Promise<JobApplication[]> {
|
||||
try {
|
||||
const q = query(collection(db, APPS_COLLECTION));
|
||||
const snapshot = await getDocs(q);
|
||||
if (snapshot.empty) return [];
|
||||
return snapshot.docs.map(docSnap => ({ id: docSnap.id, ...docSnap.data() } as JobApplication));
|
||||
} catch (err) {
|
||||
console.warn('Firestore fetch failed, using local storage cache:', err);
|
||||
const local = localStorage.getItem('ai_job_portal_applications');
|
||||
return local ? JSON.parse(local) : [];
|
||||
const res = await fetch('/api/local/data?key=' + STORAGE_KEYS.APPLICATIONS);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data.items)) return data.items;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Local API read failed, reading from browser localStorage');
|
||||
}
|
||||
const local = localStorage.getItem(STORAGE_KEYS.APPLICATIONS);
|
||||
return local ? JSON.parse(local) : [];
|
||||
}
|
||||
|
||||
export async function saveApplication(appData: Omit<JobApplication, 'id'> & { id?: string }): Promise<JobApplication> {
|
||||
const id = appData.id || `app_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
||||
const record: JobApplication = { ...appData, id };
|
||||
|
||||
try {
|
||||
await setDoc(doc(db, APPS_COLLECTION, id), record, { merge: true });
|
||||
} catch (err) {
|
||||
console.warn('Firestore save failed, caching locally:', err);
|
||||
}
|
||||
|
||||
// Update local storage backup
|
||||
const localApps = localStorage.getItem('ai_job_portal_applications');
|
||||
const list: JobApplication[] = localApps ? JSON.parse(localApps) : [];
|
||||
const list = await fetchApplications();
|
||||
const idx = list.findIndex(a => a.id === id);
|
||||
if (idx >= 0) list[idx] = record;
|
||||
else list.unshift(record);
|
||||
localStorage.setItem('ai_job_portal_applications', JSON.stringify(list));
|
||||
|
||||
localStorage.setItem(STORAGE_KEYS.APPLICATIONS, JSON.stringify(list));
|
||||
|
||||
try {
|
||||
await fetch('/api/local/data', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ key: STORAGE_KEYS.APPLICATIONS, items: list })
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Failed to sync to local disk file');
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
export async function updateApplicationStatus(id: string, newStatus: JobApplication['status'], note?: string): Promise<void> {
|
||||
const timestamp = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||
const timelineItem = { date: timestamp, action: `Status changed to ${newStatus}`, details: note };
|
||||
|
||||
try {
|
||||
const docRef = doc(db, APPS_COLLECTION, id);
|
||||
const snap = await getDoc(docRef);
|
||||
if (snap.exists()) {
|
||||
const existing = snap.data() as JobApplication;
|
||||
const timeline = existing.timeline || [];
|
||||
timeline.unshift(timelineItem);
|
||||
await updateDoc(docRef, { status: newStatus, timeline });
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Firestore update failed:', err);
|
||||
}
|
||||
|
||||
// Local storage update
|
||||
const localApps = localStorage.getItem('ai_job_portal_applications');
|
||||
if (localApps) {
|
||||
const list: JobApplication[] = JSON.parse(localApps);
|
||||
const idx = list.findIndex(a => a.id === id);
|
||||
if (idx >= 0) {
|
||||
list[idx].status = newStatus;
|
||||
if (!list[idx].timeline) list[idx].timeline = [];
|
||||
list[idx].timeline!.unshift(timelineItem);
|
||||
localStorage.setItem('ai_job_portal_applications', JSON.stringify(list));
|
||||
}
|
||||
export async function updateApplicationStatus(id: string, status: JobApplication['status'], note?: string): Promise<void> {
|
||||
const list = await fetchApplications();
|
||||
const target = list.find(a => a.id === id);
|
||||
if (target) {
|
||||
target.status = status;
|
||||
if (note) target.notes = note;
|
||||
if (!target.timeline) target.timeline = [];
|
||||
target.timeline.unshift({
|
||||
date: new Date().toLocaleDateString() + ' ' + new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||
action: `Status updated to ${status}`
|
||||
});
|
||||
await saveApplication(target);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteApplicationRecord(id: string): Promise<void> {
|
||||
try {
|
||||
await deleteDoc(doc(db, APPS_COLLECTION, id));
|
||||
} catch (err) {
|
||||
console.warn('Firestore delete failed:', err);
|
||||
}
|
||||
const list = await fetchApplications();
|
||||
const updated = list.filter(a => a.id !== id);
|
||||
localStorage.setItem(STORAGE_KEYS.APPLICATIONS, JSON.stringify(updated));
|
||||
|
||||
const localApps = localStorage.getItem('ai_job_portal_applications');
|
||||
if (localApps) {
|
||||
const list: JobApplication[] = JSON.parse(localApps);
|
||||
const filtered = list.filter(a => a.id !== id);
|
||||
localStorage.setItem('ai_job_portal_applications', JSON.stringify(filtered));
|
||||
}
|
||||
try {
|
||||
await fetch('/api/local/data', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ key: STORAGE_KEYS.APPLICATIONS, items: updated })
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export async function fetchUserProfile(): Promise<UserProfile | null> {
|
||||
try {
|
||||
const snap = await getDoc(doc(db, 'profiles', PROFILE_DOC));
|
||||
if (snap.exists()) return snap.data() as UserProfile;
|
||||
} catch (err) {
|
||||
console.warn('Firestore fetch profile failed:', err);
|
||||
}
|
||||
const local = localStorage.getItem('ai_job_portal_user_profile');
|
||||
const res = await fetch('/api/local/data?key=' + STORAGE_KEYS.USER_PROFILE);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data.profile) return data.profile;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
const local = localStorage.getItem(STORAGE_KEYS.USER_PROFILE);
|
||||
return local ? JSON.parse(local) : null;
|
||||
}
|
||||
|
||||
export async function saveUserProfile(profile: UserProfile): Promise<void> {
|
||||
localStorage.setItem(STORAGE_KEYS.USER_PROFILE, JSON.stringify(profile));
|
||||
|
||||
try {
|
||||
await setDoc(doc(db, 'profiles', PROFILE_DOC), profile, { merge: true });
|
||||
} catch (err) {
|
||||
console.warn('Firestore save profile failed:', err);
|
||||
}
|
||||
localStorage.setItem('ai_job_portal_user_profile', JSON.stringify(profile));
|
||||
await fetch('/api/local/data', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ key: STORAGE_KEYS.USER_PROFILE, profile })
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export async function fetchNotifications(): Promise<NotificationLog[]> {
|
||||
try {
|
||||
const q = query(collection(db, NOTIFICATIONS_COLLECTION));
|
||||
const snap = await getDocs(q);
|
||||
return snap.docs.map(d => ({ id: d.id, ...d.data() } as NotificationLog));
|
||||
} catch (err) {
|
||||
const local = localStorage.getItem('ai_job_portal_notifications');
|
||||
return local ? JSON.parse(local) : [];
|
||||
}
|
||||
const local = localStorage.getItem(STORAGE_KEYS.NOTIFICATIONS);
|
||||
return local ? JSON.parse(local) : [];
|
||||
}
|
||||
|
||||
export async function saveNotificationLog(notif: Omit<NotificationLog, 'id'>): Promise<NotificationLog> {
|
||||
export async function saveNotificationLog(notifData: Omit<NotificationLog, 'id'>): Promise<NotificationLog> {
|
||||
const id = `notif_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`;
|
||||
const record: NotificationLog = { ...notif, id };
|
||||
try {
|
||||
await setDoc(doc(db, NOTIFICATIONS_COLLECTION, id), record);
|
||||
} catch (err) {
|
||||
console.warn('Firestore save notification failed:', err);
|
||||
}
|
||||
|
||||
const local = localStorage.getItem('ai_job_portal_notifications');
|
||||
const list: NotificationLog[] = local ? JSON.parse(local) : [];
|
||||
const record: NotificationLog = { ...notifData, id };
|
||||
const list = await fetchNotifications();
|
||||
list.unshift(record);
|
||||
localStorage.setItem('ai_job_portal_notifications', JSON.stringify(list));
|
||||
|
||||
localStorage.setItem(STORAGE_KEYS.NOTIFICATIONS, JSON.stringify(list));
|
||||
return record;
|
||||
}
|
||||
|
||||
export async function fetchCalendarEvents(): Promise<CalendarInterviewEvent[]> {
|
||||
try {
|
||||
const snap = await getDocs(collection(db, CALENDAR_COLLECTION));
|
||||
return snap.docs.map(d => ({ id: d.id, ...d.data() } as CalendarInterviewEvent));
|
||||
} catch (err) {
|
||||
const local = localStorage.getItem('ai_job_portal_calendar');
|
||||
return local ? JSON.parse(local) : [];
|
||||
}
|
||||
const local = localStorage.getItem(STORAGE_KEYS.CALENDAR);
|
||||
return local ? JSON.parse(local) : [];
|
||||
}
|
||||
|
||||
export async function saveCalendarEvent(event: Omit<CalendarInterviewEvent, 'id'> & { id?: string }): Promise<CalendarInterviewEvent> {
|
||||
const id = event.id || `evt_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`;
|
||||
const record: CalendarInterviewEvent = { ...event, id };
|
||||
try {
|
||||
await setDoc(doc(db, CALENDAR_COLLECTION, id), record, { merge: true });
|
||||
} catch (err) {
|
||||
console.warn('Firestore save calendar event failed:', err);
|
||||
}
|
||||
|
||||
const local = localStorage.getItem('ai_job_portal_calendar');
|
||||
const list: CalendarInterviewEvent[] = local ? JSON.parse(local) : [];
|
||||
const idx = list.findIndex(e => e.id === id);
|
||||
if (idx >= 0) list[idx] = record;
|
||||
else list.push(record);
|
||||
localStorage.setItem('ai_job_portal_calendar', JSON.stringify(list));
|
||||
|
||||
export async function saveCalendarEvent(eventData: Omit<CalendarInterviewEvent, 'id'> & { id?: string }): Promise<CalendarInterviewEvent> {
|
||||
const id = eventData.id || `evt_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`;
|
||||
const record: CalendarInterviewEvent = { ...eventData, id };
|
||||
const list = await fetchCalendarEvents();
|
||||
list.push(record);
|
||||
localStorage.setItem(STORAGE_KEYS.CALENDAR, JSON.stringify(list));
|
||||
return record;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user