@lacrous/omnicloud 0.2.8 → 0.2.10
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/cli.js +8 -1
- package/dist/index.cjs +44 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/server/index.js +76 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacrous/omnicloud",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "SDK and core building blocks for OmniCloud — self-hosted cloud storage powered by your Telegram account.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"prisma": "^6.2.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@omnicloud/core": "0.2.
|
|
52
|
-
"@omnicloud/shared": "0.2.
|
|
53
|
-
"@omnicloud/telegram": "0.2.
|
|
51
|
+
"@omnicloud/core": "0.2.10",
|
|
52
|
+
"@omnicloud/shared": "0.2.10",
|
|
53
|
+
"@omnicloud/telegram": "0.2.10",
|
|
54
54
|
"@types/node": "^22.10.0",
|
|
55
55
|
"esbuild": "0.25.12",
|
|
56
56
|
"tsup": "^8.3.0",
|
package/server/index.js
CHANGED
|
@@ -761,6 +761,23 @@ function truncate(value, max) {
|
|
|
761
761
|
return value === null ? null : value.slice(0, max);
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
+
// ../core/src/services/version-retention.ts
|
|
765
|
+
function versionsToPrune(versions, currentVersionId, policy, now) {
|
|
766
|
+
const historical = versions.filter((version) => version.id !== currentVersionId).sort((a, b) => b.versionNumber - a.versionNumber);
|
|
767
|
+
switch (policy.kind) {
|
|
768
|
+
case "KEEP_ALL":
|
|
769
|
+
return [];
|
|
770
|
+
case "KEEP_LATEST_N": {
|
|
771
|
+
const keep = Math.max(0, policy.count);
|
|
772
|
+
return historical.slice(keep);
|
|
773
|
+
}
|
|
774
|
+
case "KEEP_FOR_DAYS": {
|
|
775
|
+
const cutoff = now.getTime() - policy.days * 24 * 60 * 60 * 1e3;
|
|
776
|
+
return historical.filter((version) => version.createdAt.getTime() < cutoff);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
764
781
|
// ../core/src/services/upload-operation.ts
|
|
765
782
|
function isValidOperationId(id) {
|
|
766
783
|
return /^[A-Za-z0-9_-]{8,64}$/.test(id);
|
|
@@ -1538,6 +1555,33 @@ var FileService = class {
|
|
|
1538
1555
|
};
|
|
1539
1556
|
}
|
|
1540
1557
|
// ── internals ────────────────────────────────────────────────────────────
|
|
1558
|
+
/**
|
|
1559
|
+
* Removes historical versions the policy no longer keeps. For each version the
|
|
1560
|
+
* Telegram message is deleted first, and its row only after that succeeds, the
|
|
1561
|
+
* same order as a permanent delete. A failed remote delete leaves that version
|
|
1562
|
+
* in place. The current version is never removed. Returns how many were removed.
|
|
1563
|
+
*/
|
|
1564
|
+
async pruneVersions(userId, fileId, policy, now = /* @__PURE__ */ new Date()) {
|
|
1565
|
+
const record = await this.get(userId, fileId);
|
|
1566
|
+
const versions = await this.files.listVersions(fileId);
|
|
1567
|
+
const doomed = versionsToPrune(versions, record.currentVersionId ?? null, policy, now);
|
|
1568
|
+
if (doomed.length === 0) return 0;
|
|
1569
|
+
const engine = await this.engineFor(userId);
|
|
1570
|
+
let removed = 0;
|
|
1571
|
+
for (const version of doomed) {
|
|
1572
|
+
await engine.remove({ messageId: String(version.telegramMessageId) });
|
|
1573
|
+
await this.files.deleteVersion(version.id);
|
|
1574
|
+
removed += 1;
|
|
1575
|
+
}
|
|
1576
|
+
await this.activity.record({
|
|
1577
|
+
userId,
|
|
1578
|
+
action: "delete",
|
|
1579
|
+
resourceType: "file",
|
|
1580
|
+
resourceId: fileId,
|
|
1581
|
+
resourceName: `${record.name} (${removed} old version${removed === 1 ? "" : "s"})`
|
|
1582
|
+
});
|
|
1583
|
+
return removed;
|
|
1584
|
+
}
|
|
1541
1585
|
async assertFolder(userId, folderId) {
|
|
1542
1586
|
const folder = await this.folders.findById(folderId);
|
|
1543
1587
|
if (!folder || folder.userId !== userId) {
|
|
@@ -3870,6 +3914,9 @@ function createFileRepo(prisma) {
|
|
|
3870
3914
|
async deleteVersionsByFileIds(fileIds) {
|
|
3871
3915
|
if (fileIds.length === 0) return;
|
|
3872
3916
|
await prisma.fileVersion.deleteMany({ where: { fileId: { in: fileIds } } });
|
|
3917
|
+
},
|
|
3918
|
+
async deleteVersion(versionId) {
|
|
3919
|
+
await prisma.fileVersion.deleteMany({ where: { id: versionId } });
|
|
3873
3920
|
}
|
|
3874
3921
|
};
|
|
3875
3922
|
}
|
|
@@ -4317,6 +4364,27 @@ function parseListQuery(raw) {
|
|
|
4317
4364
|
}
|
|
4318
4365
|
return { query, page: query.page, limit: query.limit };
|
|
4319
4366
|
}
|
|
4367
|
+
function parseRetentionPolicy(body) {
|
|
4368
|
+
const kind = requireString(body, "policy");
|
|
4369
|
+
if (kind === "KEEP_ALL") return { kind: "KEEP_ALL" };
|
|
4370
|
+
if (kind === "KEEP_LATEST_N") {
|
|
4371
|
+
const count = requireWholeNumber(body, "count");
|
|
4372
|
+
return { kind: "KEEP_LATEST_N", count };
|
|
4373
|
+
}
|
|
4374
|
+
if (kind === "KEEP_FOR_DAYS") {
|
|
4375
|
+
const days = requireWholeNumber(body, "days");
|
|
4376
|
+
if (days < 1) throw new ValidationError("days must be at least 1");
|
|
4377
|
+
return { kind: "KEEP_FOR_DAYS", days };
|
|
4378
|
+
}
|
|
4379
|
+
throw new ValidationError("policy must be KEEP_ALL, KEEP_LATEST_N or KEEP_FOR_DAYS");
|
|
4380
|
+
}
|
|
4381
|
+
function requireWholeNumber(body, field) {
|
|
4382
|
+
const value = body?.[field];
|
|
4383
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
4384
|
+
throw new ValidationError(`${field} must be a whole number of zero or more`);
|
|
4385
|
+
}
|
|
4386
|
+
return value;
|
|
4387
|
+
}
|
|
4320
4388
|
|
|
4321
4389
|
// ../../apps/api/src/mappers.ts
|
|
4322
4390
|
function toUserDTO(user) {
|
|
@@ -4637,6 +4705,13 @@ function registerFileRoutes(app2, container2) {
|
|
|
4637
4705
|
const versions = await container2.files.listVersions(request.user.id, id);
|
|
4638
4706
|
return { versions: versions.map((v) => toVersionDTO(v, record.currentVersionId)) };
|
|
4639
4707
|
});
|
|
4708
|
+
app2.post("/api/files/:id/versions/prune", async (request, reply) => {
|
|
4709
|
+
const { id } = request.params;
|
|
4710
|
+
const body = requireBody(request);
|
|
4711
|
+
const policy = parseRetentionPolicy(body);
|
|
4712
|
+
const removed = await container2.files.pruneVersions(request.user.id, id, policy);
|
|
4713
|
+
return reply.send({ removed });
|
|
4714
|
+
});
|
|
4640
4715
|
app2.post("/api/files/:id/replace", async (request, reply) => {
|
|
4641
4716
|
const { id } = request.params;
|
|
4642
4717
|
const part = await request.file();
|
|
@@ -4895,7 +4970,7 @@ function encryptionCheck(configured) {
|
|
|
4895
4970
|
}
|
|
4896
4971
|
|
|
4897
4972
|
// ../../apps/api/src/routes/storage.ts
|
|
4898
|
-
var VERSION = "0.2.
|
|
4973
|
+
var VERSION = "0.2.10";
|
|
4899
4974
|
function registerStorageRoutes(app2, container2) {
|
|
4900
4975
|
app2.get("/api/health", async () => {
|
|
4901
4976
|
const database = await probeDatabase(container2);
|