Compare commits
2 Commits
465e33c3b8
...
d283675230
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d283675230 | ||
|
|
8782883bea |
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"version": "f8f8affc",
|
"version": "509dd73b",
|
||||||
"timestamp": 1783434543359
|
"timestamp": 1783732495856
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo, useEffect } from "react";
|
||||||
import { useLocalAuth } from "@/contexts/LocalAuthContext";
|
import { useLocalAuth } from "@/contexts/LocalAuthContext";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
@@ -117,13 +117,23 @@ export default function AAPDashboard() {
|
|||||||
|
|
||||||
// Marquage lu/non lu
|
// Marquage lu/non lu
|
||||||
const [readIds, setReadIds] = useState<Set<number>>(new Set());
|
const [readIds, setReadIds] = useState<Set<number>>(new Set());
|
||||||
|
const readIdsQuery = trpc.aap.getReadIds.useQuery(undefined, { enabled: !!user });
|
||||||
|
useEffect(() => {
|
||||||
|
if (readIdsQuery.data?.ids) {
|
||||||
|
setReadIds(new Set(readIdsQuery.data.ids));
|
||||||
|
}
|
||||||
|
}, [readIdsQuery.data]);
|
||||||
const markAsReadMutation = trpc.aap.markAsRead.useMutation({
|
const markAsReadMutation = trpc.aap.markAsRead.useMutation({
|
||||||
onSuccess: (_, vars) => {
|
onSuccess: (_, vars) => {
|
||||||
setReadIds((prev) => { const next = new Set(prev); next.add(vars.articleId); return next; });
|
setReadIds((prev) => { const next = new Set(prev); next.add(vars.articleId); return next; });
|
||||||
|
utils.aap.getReadIds.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const markAllAsReadMutation = trpc.aap.markAllAsRead.useMutation({
|
const markAllAsReadMutation = trpc.aap.markAllAsRead.useMutation({
|
||||||
onSuccess: () => { utils.aap.unreadCount.invalidate(); },
|
onSuccess: () => {
|
||||||
|
utils.aap.unreadCount.invalidate();
|
||||||
|
utils.aap.getReadIds.invalidate();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const unreadCountQuery = trpc.aap.unreadCount.useQuery();
|
const unreadCountQuery = trpc.aap.unreadCount.useQuery();
|
||||||
const unreadCount = unreadCountQuery.data?.count ?? 0;
|
const unreadCount = unreadCountQuery.data?.count ?? 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useMemo } from "react";
|
import { useState, useMemo, useEffect } from "react";
|
||||||
import { useLocalAuth } from "@/contexts/LocalAuthContext";
|
import { useLocalAuth } from "@/contexts/LocalAuthContext";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
@@ -234,14 +234,22 @@ export default function VeilleDashboard() {
|
|||||||
|
|
||||||
// Marquage lu/non lu
|
// Marquage lu/non lu
|
||||||
const [readIds, setReadIds] = useState<Set<number>>(new Set());
|
const [readIds, setReadIds] = useState<Set<number>>(new Set());
|
||||||
|
const readIdsQuery = trpc.veille.getReadIds.useQuery(undefined, { enabled: !!user });
|
||||||
|
useEffect(() => {
|
||||||
|
if (readIdsQuery.data?.ids) {
|
||||||
|
setReadIds(new Set(readIdsQuery.data.ids));
|
||||||
|
}
|
||||||
|
}, [readIdsQuery.data]);
|
||||||
const markAsReadMutation = trpc.veille.markAsRead.useMutation({
|
const markAsReadMutation = trpc.veille.markAsRead.useMutation({
|
||||||
onSuccess: (_, vars) => {
|
onSuccess: (_, vars) => {
|
||||||
setReadIds((prev) => { const next = new Set(prev); next.add(vars.articleId); return next; });
|
setReadIds((prev) => { const next = new Set(prev); next.add(vars.articleId); return next; });
|
||||||
|
utils.veille.getReadIds.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const markAllAsReadMutation = trpc.veille.markAllAsRead.useMutation({
|
const markAllAsReadMutation = trpc.veille.markAllAsRead.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.veille.unreadCount.invalidate();
|
utils.veille.unreadCount.invalidate();
|
||||||
|
utils.veille.getReadIds.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const unreadCountQuery = trpc.veille.unreadCount.useQuery();
|
const unreadCountQuery = trpc.veille.unreadCount.useQuery();
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ export async function scheduleDailyImport() {
|
|||||||
const retentionMonths = retentionStr ? parseInt(retentionStr, 10) : 0;
|
const retentionMonths = retentionStr ? parseInt(retentionStr, 10) : 0;
|
||||||
if (retentionMonths > 0) {
|
if (retentionMonths > 0) {
|
||||||
const purged = await purgeOldArticles(retentionMonths);
|
const purged = await purgeOldArticles(retentionMonths);
|
||||||
if (purged.veille > 0 || purged.aap > 0) {
|
if (purged.veille > 0 || purged.aap > 0 || purged.tombstones > 0) {
|
||||||
console.log(`[Cron] Purge rétention (${retentionMonths} mois) — Veille: -${purged.veille} | AAP: -${purged.aap}`);
|
console.log(`[Cron] Purge rétention (${retentionMonths} mois) — Veille: -${purged.veille} | AAP: -${purged.aap} | Tombstones: -${purged.tombstones}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -236,8 +236,8 @@ async function startServer() {
|
|||||||
const retentionMonths = retentionStr ? parseInt(retentionStr, 10) : 0;
|
const retentionMonths = retentionStr ? parseInt(retentionStr, 10) : 0;
|
||||||
if (retentionMonths > 0) {
|
if (retentionMonths > 0) {
|
||||||
const purged = await purgeOldArticles(retentionMonths);
|
const purged = await purgeOldArticles(retentionMonths);
|
||||||
if (purged.veille > 0 || purged.aap > 0) {
|
if (purged.veille > 0 || purged.aap > 0 || purged.tombstones > 0) {
|
||||||
console.log(`[Init] Purge rétention (${retentionMonths} mois) — Veille: -${purged.veille} | AAP: -${purged.aap}`);
|
console.log(`[Init] Purge rétention (${retentionMonths} mois) — Veille: -${purged.veille} | AAP: -${purged.aap} | Tombstones: -${purged.tombstones}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
InsertIdea,
|
InsertIdea,
|
||||||
rssFeeds,
|
rssFeeds,
|
||||||
rssSettings,
|
rssSettings,
|
||||||
|
processedDedupKeys,
|
||||||
type InsertRssFeed,
|
type InsertRssFeed,
|
||||||
type InsertRssSettings,
|
type InsertRssSettings,
|
||||||
type ImportLog,
|
type ImportLog,
|
||||||
@@ -489,16 +490,21 @@ export async function saveRssSettings(data: Partial<Omit<InsertRssSettings, "id"
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Purge ───────────────────────────────────────────────────────────────────
|
// ─── Purge ───────────────────────────────────────────────────────────────────
|
||||||
export async function purgeOldArticles(retentionMonths: number): Promise<{ veille: number; aap: number }> {
|
export async function purgeOldArticles(retentionMonths: number): Promise<{ veille: number; aap: number; tombstones: number }> {
|
||||||
const db = await getDb();
|
const db = await getDb();
|
||||||
if (!db) throw new Error("Database not available");
|
if (!db) throw new Error("Database not available");
|
||||||
const cutoff = new Date();
|
const cutoff = new Date();
|
||||||
cutoff.setMonth(cutoff.getMonth() - retentionMonths);
|
cutoff.setMonth(cutoff.getMonth() - retentionMonths);
|
||||||
const veilleResult = await db.delete(veilleItems).where(lte(veilleItems.importedAt, cutoff));
|
const veilleResult = await db.delete(veilleItems).where(lte(veilleItems.importedAt, cutoff));
|
||||||
const aapResult = await db.delete(aapItems).where(lte(aapItems.importedAt, cutoff));
|
const aapResult = await db.delete(aapItems).where(lte(aapItems.importedAt, cutoff));
|
||||||
|
// Purge des tombstones (processed_dedup_keys) de plus de 6 mois
|
||||||
|
const tombstoneCutoff = new Date();
|
||||||
|
tombstoneCutoff.setMonth(tombstoneCutoff.getMonth() - 6);
|
||||||
|
const tombstoneResult = await db.delete(processedDedupKeys).where(lte(processedDedupKeys.processedAt, tombstoneCutoff));
|
||||||
return {
|
return {
|
||||||
veille: (veilleResult as any).affectedRows ?? 0,
|
veille: (veilleResult as any).affectedRows ?? 0,
|
||||||
aap: (aapResult as any).affectedRows ?? 0,
|
aap: (aapResult as any).affectedRows ?? 0,
|
||||||
|
tombstones: (tombstoneResult as any).affectedRows ?? 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -226,6 +226,15 @@ export const appRouter = router({
|
|||||||
const read = readRows[0]?.cnt ?? 0;
|
const read = readRows[0]?.cnt ?? 0;
|
||||||
return { count: Math.max(0, total - read) };
|
return { count: Math.max(0, total - read) };
|
||||||
}),
|
}),
|
||||||
|
getReadIds: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
const db = await getDb();
|
||||||
|
if (!db) return { ids: [] as number[] };
|
||||||
|
const rows = await db
|
||||||
|
.select({ articleId: articleReads.articleId })
|
||||||
|
.from(articleReads)
|
||||||
|
.where(and(eqDrizzle(articleReads.userId, ctx.user.id), eqDrizzle(articleReads.articleType, "veille")));
|
||||||
|
return { ids: rows.map((r: { articleId: number }) => r.articleId) };
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
// ─── AAPP ────────────────────────────────────────────────────────────────────
|
// ─── AAPP ────────────────────────────────────────────────────────────────────
|
||||||
aap: router({
|
aap: router({
|
||||||
@@ -353,8 +362,17 @@ export const appRouter = router({
|
|||||||
const read = readRows[0]?.cnt ?? 0;
|
const read = readRows[0]?.cnt ?? 0;
|
||||||
return { count: Math.max(0, total - read) };
|
return { count: Math.max(0, total - read) };
|
||||||
}),
|
}),
|
||||||
|
getReadIds: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
const db = await getDb();
|
||||||
|
if (!db) return { ids: [] as number[] };
|
||||||
|
const rows = await db
|
||||||
|
.select({ articleId: articleReads.articleId })
|
||||||
|
.from(articleReads)
|
||||||
|
.where(and(eqDrizzle(articleReads.userId, ctx.user.id), eqDrizzle(articleReads.articleType, "aap")));
|
||||||
|
return { ids: rows.map((r: { articleId: number }) => r.articleId) };
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
// ─── Importt ─────────────────────────────────────────────────────────────────
|
// ─── Import ─────────────────────────────────────────────────────────────────
|
||||||
import: router({
|
import: router({
|
||||||
run: adminProcedure
|
run: adminProcedure
|
||||||
.input(z.object({ type: z.enum(["veille", "aap", "all"]).default("all") }))
|
.input(z.object({ type: z.enum(["veille", "aap", "all"]).default("all") }))
|
||||||
|
|||||||
Reference in New Issue
Block a user