File size: 4,116 Bytes
0f84d64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import crypto from 'crypto';
import path from 'path';
import { loadEncryptedJson, saveEncryptedJson } from './cryptoUtils.js';

const DATA_ROOT = '/data/deleted_chats';
const INDEX_FILE = path.join(DATA_ROOT, 'index.json');
const RETENTION_MS = 30 * 24 * 60 * 60 * 1000;

const state = {
  loaded: false,
  index: {
    deletedChats: {},
  },
};

function nowIso() {
  return new Date().toISOString();
}

function ensureOwner(owner) {
  if (!owner?.type || !owner?.id) throw new Error('Invalid deleted chat owner');
  return owner;
}

async function ensureLoaded() {
  if (state.loaded) return;
  const stored = await loadEncryptedJson(INDEX_FILE);
  state.index = {
    deletedChats: stored?.deletedChats || {},
  };
  state.loaded = true;
  await thisStore.purgeExpired();
}

async function saveIndex() {
  await saveEncryptedJson(INDEX_FILE, state.index);
}

function matchesOwner(record, owner) {
  return record.ownerType === owner.type && record.ownerId === owner.id;
}

function sanitize(record) {
  return {
    id: record.id,
    originalSessionId: record.originalSessionId,
    name: record.name,
    deletedAt: record.deletedAt,
    purgeAt: record.purgeAt,
    created: record.created,
  };
}

const thisStore = {
  async add(owner, sessionSnapshot) {
    ensureOwner(owner);
    await ensureLoaded();

    const sessionId = sessionSnapshot?.id;
    if (!sessionId) return null;

    for (const record of Object.values(state.index.deletedChats)) {
      if (
        record.originalSessionId === sessionId &&
        record.ownerType === owner.type &&
        record.ownerId === owner.id
      ) {
        record.sessionSnapshot = sessionSnapshot;
        record.name = sessionSnapshot.name || 'Deleted Chat';
        record.created = sessionSnapshot.created || Date.now();
        record.deletedAt = nowIso();
        record.purgeAt = new Date(Date.now() + RETENTION_MS).toISOString();
        await saveIndex();
        return sanitize(record);
      }
    }

    const deleted = {
      id: crypto.randomUUID(),
      originalSessionId: sessionId,
      ownerType: owner.type,
      ownerId: owner.id,
      name: sessionSnapshot.name || 'Deleted Chat',
      created: sessionSnapshot.created || Date.now(),
      sessionSnapshot,
      deletedAt: nowIso(),
      purgeAt: new Date(Date.now() + RETENTION_MS).toISOString(),
    };
    state.index.deletedChats[deleted.id] = deleted;
    await saveIndex();
    return sanitize(deleted);
  },

  async list(owner) {
    ensureOwner(owner);
    await ensureLoaded();
    return Object.values(state.index.deletedChats)
      .filter((record) => matchesOwner(record, owner))
      .sort((a, b) => new Date(b.deletedAt).getTime() - new Date(a.deletedAt).getTime())
      .map(sanitize);
  },

  async restore(owner, ids) {
    ensureOwner(owner);
    await ensureLoaded();
    const restored = [];
    for (const id of ids || []) {
      const record = state.index.deletedChats[id];
      if (!record || !matchesOwner(record, owner)) continue;
      restored.push(record.sessionSnapshot);
      delete state.index.deletedChats[id];
    }
    if (restored.length) await saveIndex();
    return restored;
  },

  async deleteForever(owner, ids) {
    ensureOwner(owner);
    await ensureLoaded();
    const removed = [];
    for (const id of ids || []) {
      const record = state.index.deletedChats[id];
      if (!record || !matchesOwner(record, owner)) continue;
      delete state.index.deletedChats[id];
      removed.push(id);
    }
    if (removed.length) await saveIndex();
    return removed;
  },

  async purgeExpired() {
    if (!state.loaded) return;
    const now = Date.now();
    let changed = false;
    for (const [id, record] of Object.entries(state.index.deletedChats)) {
      if (new Date(record.purgeAt).getTime() <= now) {
        delete state.index.deletedChats[id];
        changed = true;
      }
    }
    if (changed) await saveIndex();
  },
};

setInterval(() => {
  thisStore.purgeExpired().catch((err) => console.error('chatTrashStore cleanup failed:', err));
}, 6 * 60 * 60 * 1000);

export const chatTrashStore = thisStore;