@seedvault/server 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.html +24 -7
- package/dist/server.js +63 -5
- package/package.json +1 -1
package/dist/index.html
CHANGED
|
@@ -404,20 +404,31 @@
|
|
|
404
404
|
const savedContributor = localStorage.getItem("sv-contributor") || "";
|
|
405
405
|
const savedFile = localStorage.getItem("sv-file") || "";
|
|
406
406
|
|
|
407
|
-
function buildTree(
|
|
407
|
+
function buildTree(fileEntries) {
|
|
408
408
|
const root = {};
|
|
409
|
-
for (const
|
|
410
|
-
const parts =
|
|
409
|
+
for (const f of fileEntries) {
|
|
410
|
+
const parts = f.path.split("/");
|
|
411
411
|
let node = root;
|
|
412
412
|
for (const part of parts) {
|
|
413
413
|
if (!node[part]) node[part] = {};
|
|
414
414
|
node = node[part];
|
|
415
415
|
}
|
|
416
|
-
node.__file =
|
|
416
|
+
node.__file = f;
|
|
417
417
|
}
|
|
418
418
|
return root;
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
function getNewestCtime(node) {
|
|
422
|
+
if (node.__file) return node.__file.originCtime || node.__file.serverCreatedAt || "";
|
|
423
|
+
let newest = "";
|
|
424
|
+
for (const key of Object.keys(node)) {
|
|
425
|
+
if (key === "__file") continue;
|
|
426
|
+
const t = getNewestCtime(node[key]);
|
|
427
|
+
if (t > newest) newest = t;
|
|
428
|
+
}
|
|
429
|
+
return newest;
|
|
430
|
+
}
|
|
431
|
+
|
|
421
432
|
function countFiles(node) {
|
|
422
433
|
let count = 0;
|
|
423
434
|
for (const key of Object.keys(node)) {
|
|
@@ -432,6 +443,9 @@
|
|
|
432
443
|
const aDir = Object.keys(node[a]).some((k) => k !== "__file");
|
|
433
444
|
const bDir = Object.keys(node[b]).some((k) => k !== "__file");
|
|
434
445
|
if (aDir !== bDir) return aDir ? -1 : 1;
|
|
446
|
+
const aTime = getNewestCtime(node[a]);
|
|
447
|
+
const bTime = getNewestCtime(node[b]);
|
|
448
|
+
if (aTime !== bTime) return bTime.localeCompare(aTime);
|
|
435
449
|
return a.localeCompare(b);
|
|
436
450
|
});
|
|
437
451
|
for (const key of keys) {
|
|
@@ -444,9 +458,9 @@
|
|
|
444
458
|
|
|
445
459
|
if (isFile) {
|
|
446
460
|
row.innerHTML = '<span class="arrow"> </span><i class="ph ph-file-text"></i> ' + key;
|
|
447
|
-
row.dataset.path = child.__file;
|
|
461
|
+
row.dataset.path = child.__file.path;
|
|
448
462
|
row.dataset.contributor = username;
|
|
449
|
-
row.onclick = () => loadContent(username, child.__file, row);
|
|
463
|
+
row.onclick = () => loadContent(username, child.__file.path, row);
|
|
450
464
|
} else {
|
|
451
465
|
const sub = document.createElement("ul");
|
|
452
466
|
const fileCount = countFiles(child);
|
|
@@ -522,7 +536,10 @@
|
|
|
522
536
|
if (!silent) status("Loading files...");
|
|
523
537
|
const { files } = await (await api("/v1/files?prefix=" + encodeURIComponent(username + "/"))).json();
|
|
524
538
|
const prefix = username + "/";
|
|
525
|
-
const tree = buildTree(files.map((f) =>
|
|
539
|
+
const tree = buildTree(files.map((f) => ({
|
|
540
|
+
...f,
|
|
541
|
+
path: f.path.startsWith(prefix) ? f.path.slice(prefix.length) : f.path,
|
|
542
|
+
})));
|
|
526
543
|
renderTree(tree, sub, username, 1);
|
|
527
544
|
markActiveRow();
|
|
528
545
|
if (!silent) status(files.length + " file(s)");
|
package/dist/server.js
CHANGED
|
@@ -40,6 +40,17 @@ function initDb(dbPath) {
|
|
|
40
40
|
used_at TEXT,
|
|
41
41
|
used_by TEXT REFERENCES contributors(username)
|
|
42
42
|
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
45
|
+
contributor TEXT NOT NULL,
|
|
46
|
+
path TEXT NOT NULL,
|
|
47
|
+
origin_ctime TEXT NOT NULL,
|
|
48
|
+
origin_mtime TEXT NOT NULL,
|
|
49
|
+
server_created_at TEXT NOT NULL,
|
|
50
|
+
server_modified_at TEXT NOT NULL,
|
|
51
|
+
PRIMARY KEY (contributor, path),
|
|
52
|
+
FOREIGN KEY (contributor) REFERENCES contributors(username)
|
|
53
|
+
);
|
|
43
54
|
`);
|
|
44
55
|
return db;
|
|
45
56
|
}
|
|
@@ -98,6 +109,34 @@ function getInvite(id) {
|
|
|
98
109
|
function markInviteUsed(id, usedBy) {
|
|
99
110
|
getDb().prepare("UPDATE invites SET used_at = ?, used_by = ? WHERE id = ?").run(new Date().toISOString(), usedBy, id);
|
|
100
111
|
}
|
|
112
|
+
function upsertFileMetadata(contributor, path, originCtime, originMtime) {
|
|
113
|
+
const now = new Date().toISOString();
|
|
114
|
+
getDb().prepare(`INSERT INTO files (contributor, path, origin_ctime, origin_mtime, server_created_at, server_modified_at)
|
|
115
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
116
|
+
ON CONFLICT (contributor, path) DO UPDATE SET
|
|
117
|
+
origin_mtime = excluded.origin_mtime,
|
|
118
|
+
server_modified_at = excluded.server_modified_at`).run(contributor, path, originCtime, originMtime, now, now);
|
|
119
|
+
return getFileMetadata(contributor, path);
|
|
120
|
+
}
|
|
121
|
+
function getFileMetadata(contributor, path) {
|
|
122
|
+
return getDb().prepare("SELECT * FROM files WHERE contributor = ? AND path = ?").get(contributor, path);
|
|
123
|
+
}
|
|
124
|
+
function listFileMetadata(contributor, prefix) {
|
|
125
|
+
const map = new Map;
|
|
126
|
+
let rows;
|
|
127
|
+
if (prefix) {
|
|
128
|
+
rows = getDb().prepare("SELECT * FROM files WHERE contributor = ? AND path LIKE ?").all(contributor, prefix + "%");
|
|
129
|
+
} else {
|
|
130
|
+
rows = getDb().prepare("SELECT * FROM files WHERE contributor = ?").all(contributor);
|
|
131
|
+
}
|
|
132
|
+
for (const row of rows) {
|
|
133
|
+
map.set(row.path, row);
|
|
134
|
+
}
|
|
135
|
+
return map;
|
|
136
|
+
}
|
|
137
|
+
function deleteFileMetadata(contributor, path) {
|
|
138
|
+
getDb().prepare("DELETE FROM files WHERE contributor = ? AND path = ?").run(contributor, path);
|
|
139
|
+
}
|
|
101
140
|
|
|
102
141
|
// ../node_modules/.bun/hono@4.11.9/node_modules/hono/dist/compose.js
|
|
103
142
|
var compose = (middleware, onError, onNotFound) => {
|
|
@@ -2128,8 +2167,12 @@ function createApp(storageRoot) {
|
|
|
2128
2167
|
return c.json({ error: pathError }, 400);
|
|
2129
2168
|
}
|
|
2130
2169
|
const content = await c.req.text();
|
|
2170
|
+
const now = new Date().toISOString();
|
|
2171
|
+
const originCtime = c.req.header("X-Origin-Ctime") || now;
|
|
2172
|
+
const originMtime = c.req.header("X-Origin-Mtime") || now;
|
|
2131
2173
|
try {
|
|
2132
2174
|
const result = await writeFileAtomic(storageRoot, parsed.username, parsed.filePath, content);
|
|
2175
|
+
const meta = upsertFileMetadata(parsed.username, parsed.filePath, originCtime, originMtime);
|
|
2133
2176
|
broadcast("file_updated", {
|
|
2134
2177
|
contributor: parsed.username,
|
|
2135
2178
|
path: result.path,
|
|
@@ -2137,7 +2180,13 @@ function createApp(storageRoot) {
|
|
|
2137
2180
|
modifiedAt: result.modifiedAt
|
|
2138
2181
|
});
|
|
2139
2182
|
triggerUpdate();
|
|
2140
|
-
return c.json(
|
|
2183
|
+
return c.json({
|
|
2184
|
+
...result,
|
|
2185
|
+
originCtime: meta.origin_ctime,
|
|
2186
|
+
originMtime: meta.origin_mtime,
|
|
2187
|
+
serverCreatedAt: meta.server_created_at,
|
|
2188
|
+
serverModifiedAt: meta.server_modified_at
|
|
2189
|
+
});
|
|
2141
2190
|
} catch (e) {
|
|
2142
2191
|
if (e instanceof FileTooLargeError) {
|
|
2143
2192
|
return c.json({ error: e.message }, 413);
|
|
@@ -2160,6 +2209,7 @@ function createApp(storageRoot) {
|
|
|
2160
2209
|
}
|
|
2161
2210
|
try {
|
|
2162
2211
|
await deleteFile(storageRoot, parsed.username, parsed.filePath);
|
|
2212
|
+
deleteFileMetadata(parsed.username, parsed.filePath);
|
|
2163
2213
|
broadcast("file_deleted", {
|
|
2164
2214
|
contributor: parsed.username,
|
|
2165
2215
|
path: parsed.filePath
|
|
@@ -2185,11 +2235,19 @@ function createApp(storageRoot) {
|
|
|
2185
2235
|
return c.json({ error: "Contributor not found" }, 404);
|
|
2186
2236
|
}
|
|
2187
2237
|
const files = await listFiles(storageRoot, username, subPrefix);
|
|
2238
|
+
const metaMap = listFileMetadata(username, subPrefix);
|
|
2188
2239
|
return c.json({
|
|
2189
|
-
files: files.map((f) =>
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2240
|
+
files: files.map((f) => {
|
|
2241
|
+
const meta = metaMap.get(f.path);
|
|
2242
|
+
return {
|
|
2243
|
+
...f,
|
|
2244
|
+
path: `${username}/${f.path}`,
|
|
2245
|
+
originCtime: meta?.origin_ctime,
|
|
2246
|
+
originMtime: meta?.origin_mtime,
|
|
2247
|
+
serverCreatedAt: meta?.server_created_at,
|
|
2248
|
+
serverModifiedAt: meta?.server_modified_at
|
|
2249
|
+
};
|
|
2250
|
+
})
|
|
2193
2251
|
});
|
|
2194
2252
|
});
|
|
2195
2253
|
authed.get("/v1/events", (c) => {
|