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
|
// API 10: Google Drive - List Job Application Artifacts
|
||||||
app.get("/api/workspace/drive/files", async (req, res) => {
|
app.get("/api/workspace/drive/files", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+79
-132
@@ -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';
|
import { JobApplication, UserProfile, NotificationLog, CalendarInterviewEvent } from '../types';
|
||||||
|
|
||||||
const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApp();
|
export const isFirebaseConfigured = false;
|
||||||
export const db = getFirestore(app);
|
|
||||||
|
|
||||||
const APPS_COLLECTION = 'applications';
|
// 100% Pure Local Disk & LocalStorage Persistence System
|
||||||
const PROFILE_DOC = 'user_profile';
|
|
||||||
const NOTIFICATIONS_COLLECTION = 'notifications';
|
|
||||||
const CALENDAR_COLLECTION = 'calendar_events';
|
|
||||||
|
|
||||||
export const isFirebaseConfigured = true;
|
const STORAGE_KEYS = {
|
||||||
|
APPLICATIONS: 'ai_job_portal_applications',
|
||||||
// Firestore Helper Functions with LocalStorage Sync Fallback
|
USER_PROFILE: 'ai_job_portal_profile',
|
||||||
|
NOTIFICATIONS: 'ai_job_portal_notifications',
|
||||||
|
CALENDAR: 'ai_job_portal_calendar'
|
||||||
|
};
|
||||||
|
|
||||||
export async function fetchApplications(): Promise<JobApplication[]> {
|
export async function fetchApplications(): Promise<JobApplication[]> {
|
||||||
try {
|
try {
|
||||||
const q = query(collection(db, APPS_COLLECTION));
|
const res = await fetch('/api/local/data?key=' + STORAGE_KEYS.APPLICATIONS);
|
||||||
const snapshot = await getDocs(q);
|
if (res.ok) {
|
||||||
if (snapshot.empty) return [];
|
const data = await res.json();
|
||||||
return snapshot.docs.map(docSnap => ({ id: docSnap.id, ...docSnap.data() } as JobApplication));
|
if (Array.isArray(data.items)) return data.items;
|
||||||
} 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) : [];
|
|
||||||
}
|
}
|
||||||
|
} 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> {
|
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 id = appData.id || `app_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
||||||
const record: JobApplication = { ...appData, id };
|
const record: JobApplication = { ...appData, id };
|
||||||
|
|
||||||
try {
|
const list = await fetchApplications();
|
||||||
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 idx = list.findIndex(a => a.id === id);
|
const idx = list.findIndex(a => a.id === id);
|
||||||
if (idx >= 0) list[idx] = record;
|
if (idx >= 0) list[idx] = record;
|
||||||
else list.unshift(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;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateApplicationStatus(id: string, newStatus: JobApplication['status'], note?: string): Promise<void> {
|
export async function updateApplicationStatus(id: string, status: JobApplication['status'], note?: string): Promise<void> {
|
||||||
const timestamp = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
const list = await fetchApplications();
|
||||||
const timelineItem = { date: timestamp, action: `Status changed to ${newStatus}`, details: note };
|
const target = list.find(a => a.id === id);
|
||||||
|
if (target) {
|
||||||
try {
|
target.status = status;
|
||||||
const docRef = doc(db, APPS_COLLECTION, id);
|
if (note) target.notes = note;
|
||||||
const snap = await getDoc(docRef);
|
if (!target.timeline) target.timeline = [];
|
||||||
if (snap.exists()) {
|
target.timeline.unshift({
|
||||||
const existing = snap.data() as JobApplication;
|
date: new Date().toLocaleDateString() + ' ' + new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
|
||||||
const timeline = existing.timeline || [];
|
action: `Status updated to ${status}`
|
||||||
timeline.unshift(timelineItem);
|
});
|
||||||
await updateDoc(docRef, { status: newStatus, timeline });
|
await saveApplication(target);
|
||||||
}
|
|
||||||
} 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 deleteApplicationRecord(id: string): Promise<void> {
|
export async function deleteApplicationRecord(id: string): Promise<void> {
|
||||||
try {
|
const list = await fetchApplications();
|
||||||
await deleteDoc(doc(db, APPS_COLLECTION, id));
|
const updated = list.filter(a => a.id !== id);
|
||||||
} catch (err) {
|
localStorage.setItem(STORAGE_KEYS.APPLICATIONS, JSON.stringify(updated));
|
||||||
console.warn('Firestore delete failed:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
const localApps = localStorage.getItem('ai_job_portal_applications');
|
try {
|
||||||
if (localApps) {
|
await fetch('/api/local/data', {
|
||||||
const list: JobApplication[] = JSON.parse(localApps);
|
method: 'POST',
|
||||||
const filtered = list.filter(a => a.id !== id);
|
headers: { 'Content-Type': 'application/json' },
|
||||||
localStorage.setItem('ai_job_portal_applications', JSON.stringify(filtered));
|
body: JSON.stringify({ key: STORAGE_KEYS.APPLICATIONS, items: updated })
|
||||||
}
|
});
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUserProfile(): Promise<UserProfile | null> {
|
export async function fetchUserProfile(): Promise<UserProfile | null> {
|
||||||
try {
|
try {
|
||||||
const snap = await getDoc(doc(db, 'profiles', PROFILE_DOC));
|
const res = await fetch('/api/local/data?key=' + STORAGE_KEYS.USER_PROFILE);
|
||||||
if (snap.exists()) return snap.data() as UserProfile;
|
if (res.ok) {
|
||||||
} catch (err) {
|
const data = await res.json();
|
||||||
console.warn('Firestore fetch profile failed:', err);
|
if (data.profile) return data.profile;
|
||||||
}
|
}
|
||||||
const local = localStorage.getItem('ai_job_portal_user_profile');
|
} catch (e) {}
|
||||||
|
|
||||||
|
const local = localStorage.getItem(STORAGE_KEYS.USER_PROFILE);
|
||||||
return local ? JSON.parse(local) : null;
|
return local ? JSON.parse(local) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveUserProfile(profile: UserProfile): Promise<void> {
|
export async function saveUserProfile(profile: UserProfile): Promise<void> {
|
||||||
|
localStorage.setItem(STORAGE_KEYS.USER_PROFILE, JSON.stringify(profile));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setDoc(doc(db, 'profiles', PROFILE_DOC), profile, { merge: true });
|
await fetch('/api/local/data', {
|
||||||
} catch (err) {
|
method: 'POST',
|
||||||
console.warn('Firestore save profile failed:', err);
|
headers: { 'Content-Type': 'application/json' },
|
||||||
}
|
body: JSON.stringify({ key: STORAGE_KEYS.USER_PROFILE, profile })
|
||||||
localStorage.setItem('ai_job_portal_user_profile', JSON.stringify(profile));
|
});
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchNotifications(): Promise<NotificationLog[]> {
|
export async function fetchNotifications(): Promise<NotificationLog[]> {
|
||||||
try {
|
const local = localStorage.getItem(STORAGE_KEYS.NOTIFICATIONS);
|
||||||
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) : [];
|
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 id = `notif_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`;
|
||||||
const record: NotificationLog = { ...notif, id };
|
const record: NotificationLog = { ...notifData, id };
|
||||||
try {
|
const list = await fetchNotifications();
|
||||||
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) : [];
|
|
||||||
list.unshift(record);
|
list.unshift(record);
|
||||||
localStorage.setItem('ai_job_portal_notifications', JSON.stringify(list));
|
localStorage.setItem(STORAGE_KEYS.NOTIFICATIONS, JSON.stringify(list));
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCalendarEvents(): Promise<CalendarInterviewEvent[]> {
|
export async function fetchCalendarEvents(): Promise<CalendarInterviewEvent[]> {
|
||||||
try {
|
const local = localStorage.getItem(STORAGE_KEYS.CALENDAR);
|
||||||
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) : [];
|
return local ? JSON.parse(local) : [];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveCalendarEvent(event: Omit<CalendarInterviewEvent, 'id'> & { id?: string }): Promise<CalendarInterviewEvent> {
|
export async function saveCalendarEvent(eventData: Omit<CalendarInterviewEvent, 'id'> & { id?: string }): Promise<CalendarInterviewEvent> {
|
||||||
const id = event.id || `evt_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`;
|
const id = eventData.id || `evt_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`;
|
||||||
const record: CalendarInterviewEvent = { ...event, id };
|
const record: CalendarInterviewEvent = { ...eventData, id };
|
||||||
try {
|
const list = await fetchCalendarEvents();
|
||||||
await setDoc(doc(db, CALENDAR_COLLECTION, id), record, { merge: true });
|
list.push(record);
|
||||||
} catch (err) {
|
localStorage.setItem(STORAGE_KEYS.CALENDAR, JSON.stringify(list));
|
||||||
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));
|
|
||||||
|
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user