Verbesserte
This commit is contained in:
196
server.js
196
server.js
@@ -2,6 +2,7 @@ const express = require('express');
|
||||
const path = require('path');
|
||||
const cors = require('cors');
|
||||
|
||||
const { v4: uuid } = require('uuid');
|
||||
const sessionStore = require('./services/sessionStore');
|
||||
const credentialStore = require('./services/credentialStore');
|
||||
const { readConfig, writeConfig } = require('./services/configStore');
|
||||
@@ -15,6 +16,7 @@ const SIXTY_DAYS_MS = 60 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
const storeRefreshJobs = new Map();
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
@@ -83,30 +85,124 @@ function mergeStoresIntoConfig(config = [], stores = []) {
|
||||
return { merged: Array.from(map.values()), changed };
|
||||
}
|
||||
|
||||
async function loadStoresForSession(session, settings, { forceRefresh = false } = {}) {
|
||||
function isStoreCacheFresh(session) {
|
||||
if (!session?.storesCache?.fetchedAt) {
|
||||
return false;
|
||||
}
|
||||
return Date.now() - session.storesCache.fetchedAt <= SIXTY_DAYS_MS;
|
||||
}
|
||||
|
||||
function summarizeJob(job) {
|
||||
if (!job) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: job.id,
|
||||
status: job.status,
|
||||
processed: job.processed,
|
||||
total: job.total,
|
||||
currentStore: job.currentStore,
|
||||
startedAt: job.startedAt,
|
||||
finishedAt: job.finishedAt,
|
||||
reason: job.reason || null,
|
||||
error: job.error || null
|
||||
};
|
||||
}
|
||||
|
||||
function getStoreRefreshJob(sessionId) {
|
||||
const job = storeRefreshJobs.get(sessionId);
|
||||
if (!job) {
|
||||
return null;
|
||||
}
|
||||
if (job.status === 'done' && Date.now() - (job.finishedAt || 0) > 5 * 60 * 1000) {
|
||||
storeRefreshJobs.delete(sessionId);
|
||||
return null;
|
||||
}
|
||||
return job;
|
||||
}
|
||||
|
||||
function triggerStoreRefresh(session, { force = false, reason } = {}) {
|
||||
if (!session?.id) {
|
||||
return { started: false };
|
||||
}
|
||||
const existing = getStoreRefreshJob(session.id);
|
||||
if (existing && existing.status === 'running') {
|
||||
return { started: false, job: existing };
|
||||
}
|
||||
|
||||
const cacheFresh = isStoreCacheFresh(session);
|
||||
if (!force && cacheFresh) {
|
||||
return { started: false, cacheFresh: true };
|
||||
}
|
||||
|
||||
const job = {
|
||||
id: uuid(),
|
||||
status: 'queued',
|
||||
processed: 0,
|
||||
total: 0,
|
||||
currentStore: null,
|
||||
startedAt: null,
|
||||
finishedAt: null,
|
||||
reason: reason || null,
|
||||
error: null
|
||||
};
|
||||
|
||||
storeRefreshJobs.set(session.id, job);
|
||||
runStoreRefreshJob(session, job).catch((error) => {
|
||||
console.error('[STORE-REFRESH] Job fehlgeschlagen:', error.message);
|
||||
job.status = 'error';
|
||||
job.error = error.message;
|
||||
job.finishedAt = Date.now();
|
||||
});
|
||||
return { started: true, job };
|
||||
}
|
||||
|
||||
async function runStoreRefreshJob(session, job) {
|
||||
job.status = 'running';
|
||||
job.startedAt = Date.now();
|
||||
const settings = adminConfig.readSettings();
|
||||
const stores = await foodsharingClient.fetchStores(session.cookieHeader, session.profile.id, {
|
||||
delayBetweenRequestsMs: settings.storePickupCheckDelayMs,
|
||||
onStoreCheck: (store, processed, total) => {
|
||||
job.processed = processed;
|
||||
job.total = total;
|
||||
job.currentStore = store.name || `Store ${store.id}`;
|
||||
}
|
||||
});
|
||||
job.processed = stores.length;
|
||||
job.total = stores.length;
|
||||
job.currentStore = null;
|
||||
|
||||
sessionStore.update(session.id, {
|
||||
storesCache: { data: stores, fetchedAt: Date.now() }
|
||||
});
|
||||
|
||||
let config = readConfig(session.profile.id);
|
||||
const { merged, changed } = mergeStoresIntoConfig(config, stores);
|
||||
if (changed) {
|
||||
config = merged;
|
||||
writeConfig(session.profile.id, config);
|
||||
scheduleWithCurrentSettings(session.id, config);
|
||||
}
|
||||
|
||||
job.status = 'done';
|
||||
job.finishedAt = Date.now();
|
||||
}
|
||||
|
||||
async function loadStoresForSession(session, _settings, { forceRefresh = false, reason } = {}) {
|
||||
if (!session?.profile?.id) {
|
||||
return { stores: [], refreshed: false };
|
||||
}
|
||||
|
||||
const cache = session.storesCache;
|
||||
const now = Date.now();
|
||||
const isCacheValid =
|
||||
cache &&
|
||||
Array.isArray(cache.data) &&
|
||||
Number.isFinite(cache.fetchedAt) &&
|
||||
now - cache.fetchedAt <= SIXTY_DAYS_MS;
|
||||
|
||||
if (isCacheValid && !forceRefresh) {
|
||||
return { stores: cache.data, refreshed: false };
|
||||
const cacheFresh = isStoreCacheFresh(session);
|
||||
if ((forceRefresh || !cacheFresh) && session.cookieHeader) {
|
||||
triggerStoreRefresh(session, { force: true, reason: reason || 'session-check' });
|
||||
}
|
||||
|
||||
const stores = await foodsharingClient.fetchStores(session.cookieHeader, session.profile.id, {
|
||||
delayBetweenRequestsMs: settings.storePickupCheckDelayMs
|
||||
});
|
||||
sessionStore.update(session.id, {
|
||||
storesCache: { data: stores, fetchedAt: now }
|
||||
});
|
||||
return { stores, refreshed: true };
|
||||
return {
|
||||
stores: session.storesCache?.data || [],
|
||||
refreshed: cacheFresh
|
||||
};
|
||||
}
|
||||
|
||||
async function restoreSessionsFromDisk() {
|
||||
@@ -202,16 +298,7 @@ app.post('/api/auth/login', async (req, res) => {
|
||||
};
|
||||
const isAdminUser = isAdmin(profile);
|
||||
const settings = adminConfig.readSettings();
|
||||
|
||||
let config = readConfig(profile.id);
|
||||
const stores = await foodsharingClient.fetchStores(auth.cookieHeader, profile.id, {
|
||||
delayBetweenRequestsMs: settings.storePickupCheckDelayMs
|
||||
});
|
||||
const { merged, changed } = mergeStoresIntoConfig(config, stores);
|
||||
if (changed) {
|
||||
config = merged;
|
||||
writeConfig(profile.id, config);
|
||||
}
|
||||
|
||||
const existingCredentials = credentialStore.get(profile.id);
|
||||
const existingToken = existingCredentials?.token;
|
||||
@@ -228,18 +315,18 @@ app.post('/api/auth/login', async (req, res) => {
|
||||
}, existingToken, ONE_YEAR_MS);
|
||||
|
||||
credentialStore.save(profile.id, { email, password, token: session.id });
|
||||
sessionStore.update(session.id, {
|
||||
storesCache: { data: stores, fetchedAt: Date.now() }
|
||||
});
|
||||
scheduleConfig(session.id, config, settings);
|
||||
const refreshResult = triggerStoreRefresh(session, { force: true, reason: 'login' });
|
||||
|
||||
return res.json({
|
||||
token: session.id,
|
||||
profile,
|
||||
stores,
|
||||
stores: session.storesCache?.data || [],
|
||||
config,
|
||||
isAdmin: isAdminUser,
|
||||
adminSettings: isAdminUser ? settings : undefined
|
||||
adminSettings: isAdminUser ? settings : undefined,
|
||||
storeRefreshJob: summarizeJob(refreshResult.job),
|
||||
storesFresh: isStoreCacheFresh(session)
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login fehlgeschlagen:', error.message);
|
||||
@@ -250,24 +337,20 @@ app.post('/api/auth/login', async (req, res) => {
|
||||
app.post('/api/auth/logout', requireAuth, (req, res) => {
|
||||
sessionStore.delete(req.session.id);
|
||||
credentialStore.remove(req.session.profile.id);
|
||||
storeRefreshJobs.delete(req.session.id);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.get('/api/auth/session', requireAuth, async (req, res) => {
|
||||
const settings = adminConfig.readSettings();
|
||||
const { stores, refreshed } = await loadStoresForSession(req.session, settings);
|
||||
if (refreshed) {
|
||||
let config = readConfig(req.session.profile.id);
|
||||
const { merged, changed } = mergeStoresIntoConfig(config, stores);
|
||||
if (changed) {
|
||||
writeConfig(req.session.profile.id, merged);
|
||||
}
|
||||
}
|
||||
const { stores } = await loadStoresForSession(req.session, settings, { reason: 'session-check' });
|
||||
res.json({
|
||||
profile: req.session.profile,
|
||||
stores,
|
||||
isAdmin: !!req.session.isAdmin,
|
||||
adminSettings: req.session.isAdmin ? settings : undefined
|
||||
adminSettings: req.session.isAdmin ? settings : undefined,
|
||||
storeRefreshJob: summarizeJob(getStoreRefreshJob(req.session.id)),
|
||||
storesFresh: isStoreCacheFresh(req.session)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -293,16 +376,27 @@ app.post('/api/config', requireAuth, (req, res) => {
|
||||
});
|
||||
|
||||
app.get('/api/stores', requireAuth, async (req, res) => {
|
||||
const settings = adminConfig.readSettings();
|
||||
const { stores, refreshed } = await loadStoresForSession(req.session, settings, { forceRefresh: true });
|
||||
if (refreshed) {
|
||||
let config = readConfig(req.session.profile.id);
|
||||
const { merged, changed } = mergeStoresIntoConfig(config, stores);
|
||||
if (changed) {
|
||||
writeConfig(req.session.profile.id, merged);
|
||||
}
|
||||
}
|
||||
res.json(stores);
|
||||
res.json(req.session.storesCache?.data || []);
|
||||
});
|
||||
|
||||
app.post('/api/stores/refresh', requireAuth, (req, res) => {
|
||||
const force = req.body?.force !== undefined ? !!req.body.force : true;
|
||||
const reason = req.body?.reason || 'manual';
|
||||
const result = triggerStoreRefresh(req.session, { force, reason });
|
||||
res.json({
|
||||
started: !!result.started,
|
||||
storesFresh: isStoreCacheFresh(req.session),
|
||||
job: summarizeJob(result.job)
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/stores/refresh/status', requireAuth, (req, res) => {
|
||||
const job = getStoreRefreshJob(req.session.id);
|
||||
res.json({
|
||||
job: summarizeJob(job),
|
||||
storesFresh: isStoreCacheFresh(req.session),
|
||||
cacheUpdatedAt: req.session.storesCache?.fetchedAt || null
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/admin/settings', requireAuth, requireAdmin, (_req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user