@mzedstudio/uploadthingtrack 0.1.0
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/LICENSE +176 -0
- package/README.md +264 -0
- package/dist/client/index.d.ts +157 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +121 -0
- package/dist/client/index.js.map +1 -0
- package/dist/component/_generated/api.d.ts +48 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +119 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/access.d.ts +9 -0
- package/dist/component/access.d.ts.map +1 -0
- package/dist/component/access.js +31 -0
- package/dist/component/access.js.map +1 -0
- package/dist/component/callbacks.d.ts +16 -0
- package/dist/component/callbacks.d.ts.map +1 -0
- package/dist/component/callbacks.js +147 -0
- package/dist/component/callbacks.js.map +1 -0
- package/dist/component/cleanup.d.ts +9 -0
- package/dist/component/cleanup.d.ts.map +1 -0
- package/dist/component/cleanup.js +32 -0
- package/dist/component/cleanup.js.map +1 -0
- package/dist/component/config.d.ts +42 -0
- package/dist/component/config.d.ts.map +1 -0
- package/dist/component/config.js +87 -0
- package/dist/component/config.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +3 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/files.d.ts +72 -0
- package/dist/component/files.d.ts.map +1 -0
- package/dist/component/files.js +153 -0
- package/dist/component/files.js.map +1 -0
- package/dist/component/queries.d.ts +43 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +113 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/schema.d.ts +97 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +42 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/stats.d.ts +7 -0
- package/dist/component/stats.d.ts.map +1 -0
- package/dist/component/stats.js +20 -0
- package/dist/component/stats.js.map +1 -0
- package/dist/component/types.d.ts +78 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component/types.js +34 -0
- package/dist/component/types.js.map +1 -0
- package/package.json +84 -0
- package/src/client/index.ts +277 -0
- package/src/component/_generated/api.ts +64 -0
- package/src/component/_generated/component.ts +159 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/access.ts +39 -0
- package/src/component/callbacks.ts +173 -0
- package/src/component/cleanup.ts +40 -0
- package/src/component/config.ts +115 -0
- package/src/component/convex.config.ts +2 -0
- package/src/component/files.ts +186 -0
- package/src/component/queries.ts +121 -0
- package/src/component/schema.ts +44 -0
- package/src/component/stats.ts +23 -0
- package/src/component/types.ts +49 -0
- package/src/test.ts +21 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function canAccess(params) {
|
|
2
|
+
const { ownerId, viewerId } = params;
|
|
3
|
+
if (viewerId && viewerId === ownerId)
|
|
4
|
+
return true;
|
|
5
|
+
const rule = params.fileRule ?? params.folderRule;
|
|
6
|
+
if (!rule)
|
|
7
|
+
return false;
|
|
8
|
+
if (viewerId && rule.denyUserIds?.includes(viewerId))
|
|
9
|
+
return false;
|
|
10
|
+
if (rule.visibility === "public")
|
|
11
|
+
return true;
|
|
12
|
+
if (!viewerId)
|
|
13
|
+
return false;
|
|
14
|
+
if (rule.visibility === "private")
|
|
15
|
+
return false;
|
|
16
|
+
if (rule.visibility === "restricted") {
|
|
17
|
+
return Boolean(rule.allowUserIds?.includes(viewerId));
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
export function sanitizeAccessRule(rule) {
|
|
22
|
+
if (!rule)
|
|
23
|
+
return undefined;
|
|
24
|
+
const unique = (list) => list ? Array.from(new Set(list.filter((value) => value.length > 0))) : undefined;
|
|
25
|
+
return {
|
|
26
|
+
visibility: rule.visibility,
|
|
27
|
+
allowUserIds: unique(rule.allowUserIds),
|
|
28
|
+
denyUserIds: unique(rule.denyUserIds),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../../src/component/access.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CAAC,MAKzB;IACC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACrC,IAAI,QAAQ,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAElD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC;IAClD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IAExB,IAAI,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAEnE,IAAI,IAAI,CAAC,UAAU,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE9C,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE5B,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAEhD,IAAI,IAAI,CAAC,UAAU,KAAK,YAAY,EAAE,CAAC;QACrC,OAAO,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAiB;IAClD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,MAAM,MAAM,GAAG,CAAC,IAAe,EAAE,EAAE,CACjC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;QACvC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;KACtC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type CallbackResult = {
|
|
2
|
+
ok: true;
|
|
3
|
+
fileId: string;
|
|
4
|
+
hook: string;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
error: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const handleUploadthingCallback: import("convex/server").RegisteredAction<"public", {
|
|
10
|
+
apiKey?: string | undefined;
|
|
11
|
+
hook: string;
|
|
12
|
+
rawBody: string;
|
|
13
|
+
signature: string;
|
|
14
|
+
}, Promise<CallbackResult>>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=callbacks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callbacks.d.ts","sourceRoot":"","sources":["../../src/component/callbacks.ts"],"names":[],"mappings":"AAgGA,KAAK,cAAc,GACf;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,eAAO,MAAM,yBAAyB;;;;;2BAwEpC,CAAC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { action } from "./_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { internal } from "./_generated/api";
|
|
4
|
+
function normalizeSignature(signature) {
|
|
5
|
+
const trimmed = signature.trim();
|
|
6
|
+
if (trimmed.startsWith("hmac-sha256=")) {
|
|
7
|
+
return trimmed.slice("hmac-sha256=".length);
|
|
8
|
+
}
|
|
9
|
+
return trimmed;
|
|
10
|
+
}
|
|
11
|
+
function hexToBytes(hex) {
|
|
12
|
+
if (hex.length % 2 !== 0)
|
|
13
|
+
return null;
|
|
14
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
15
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
16
|
+
const chunk = hex.slice(i * 2, i * 2 + 2);
|
|
17
|
+
const value = Number.parseInt(chunk, 16);
|
|
18
|
+
if (Number.isNaN(value))
|
|
19
|
+
return null;
|
|
20
|
+
bytes[i] = value;
|
|
21
|
+
}
|
|
22
|
+
return bytes;
|
|
23
|
+
}
|
|
24
|
+
function timingSafeEqualBytes(a, b) {
|
|
25
|
+
if (a.length !== b.length)
|
|
26
|
+
return false;
|
|
27
|
+
let diff = 0;
|
|
28
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
29
|
+
diff |= a[i] ^ b[i];
|
|
30
|
+
}
|
|
31
|
+
return diff === 0;
|
|
32
|
+
}
|
|
33
|
+
async function hmacSha256Hex(key, message) {
|
|
34
|
+
if (!globalThis.crypto?.subtle) {
|
|
35
|
+
throw new Error("WebCrypto unavailable in this runtime.");
|
|
36
|
+
}
|
|
37
|
+
const encoder = new TextEncoder();
|
|
38
|
+
const cryptoKey = await globalThis.crypto.subtle.importKey("raw", encoder.encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
39
|
+
const signature = await globalThis.crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(message));
|
|
40
|
+
const bytes = new Uint8Array(signature);
|
|
41
|
+
return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
42
|
+
}
|
|
43
|
+
async function verifySignature(rawBody, signature, apiKey) {
|
|
44
|
+
const expected = await hmacSha256Hex(apiKey, rawBody);
|
|
45
|
+
const actual = normalizeSignature(signature);
|
|
46
|
+
const expectedBytes = hexToBytes(expected);
|
|
47
|
+
const actualBytes = hexToBytes(actual);
|
|
48
|
+
if (!expectedBytes || !actualBytes)
|
|
49
|
+
return false;
|
|
50
|
+
return timingSafeEqualBytes(expectedBytes, actualBytes);
|
|
51
|
+
}
|
|
52
|
+
function extractUserId(metadata) {
|
|
53
|
+
if (!metadata)
|
|
54
|
+
return undefined;
|
|
55
|
+
return (metadata.userId ??
|
|
56
|
+
metadata.ownerId ??
|
|
57
|
+
metadata.uploadedBy ??
|
|
58
|
+
metadata.user);
|
|
59
|
+
}
|
|
60
|
+
function extractTags(metadata) {
|
|
61
|
+
if (!metadata)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (Array.isArray(metadata.tags))
|
|
64
|
+
return metadata.tags;
|
|
65
|
+
if (typeof metadata.tags === "string") {
|
|
66
|
+
return metadata.tags
|
|
67
|
+
.split(",")
|
|
68
|
+
.map((tag) => tag.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
function toNumber(value) {
|
|
74
|
+
if (typeof value === "number")
|
|
75
|
+
return value;
|
|
76
|
+
if (typeof value === "string") {
|
|
77
|
+
const parsed = Number(value);
|
|
78
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
export const handleUploadthingCallback = action({
|
|
83
|
+
args: {
|
|
84
|
+
rawBody: v.string(),
|
|
85
|
+
signature: v.string(),
|
|
86
|
+
hook: v.string(),
|
|
87
|
+
apiKey: v.optional(v.string()),
|
|
88
|
+
},
|
|
89
|
+
handler: async (ctx, args) => {
|
|
90
|
+
const globals = await ctx.runQuery(internal.config.getGlobalsInternal, {});
|
|
91
|
+
const apiKey = args.apiKey ?? globals.uploadthingApiKey;
|
|
92
|
+
if (!apiKey) {
|
|
93
|
+
throw new Error("UploadThing API key not configured.");
|
|
94
|
+
}
|
|
95
|
+
const isValid = await verifySignature(args.rawBody, args.signature, apiKey);
|
|
96
|
+
if (!isValid) {
|
|
97
|
+
return { ok: false, error: "invalid_signature" };
|
|
98
|
+
}
|
|
99
|
+
let payload;
|
|
100
|
+
try {
|
|
101
|
+
payload = JSON.parse(args.rawBody);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return { ok: false, error: "invalid_json" };
|
|
105
|
+
}
|
|
106
|
+
const file = payload.file ?? payload;
|
|
107
|
+
const metadata = payload.metadata ?? file.metadata ?? {};
|
|
108
|
+
const key = file.key ?? file.fileKey ?? file.id;
|
|
109
|
+
const url = file.url ?? file.fileUrl;
|
|
110
|
+
const name = file.name ?? file.filename ?? file.fileName;
|
|
111
|
+
const size = toNumber(file.size ?? file.fileSize);
|
|
112
|
+
const mimeType = file.type ?? file.mimeType ?? file.contentType;
|
|
113
|
+
const customId = file.customId ?? file.customID;
|
|
114
|
+
const fileType = file.fileType ?? metadata.fileType;
|
|
115
|
+
if (!key || !url || !name || size === undefined || !mimeType) {
|
|
116
|
+
return { ok: false, error: "missing_file_fields" };
|
|
117
|
+
}
|
|
118
|
+
const userId = extractUserId(metadata) ?? payload.userId;
|
|
119
|
+
if (!userId) {
|
|
120
|
+
return { ok: false, error: "missing_user_id" };
|
|
121
|
+
}
|
|
122
|
+
const options = {
|
|
123
|
+
tags: extractTags(metadata),
|
|
124
|
+
folder: metadata.folder,
|
|
125
|
+
access: metadata.access,
|
|
126
|
+
metadata,
|
|
127
|
+
expiresAt: toNumber(metadata.expiresAt),
|
|
128
|
+
ttlMs: toNumber(metadata.ttlMs),
|
|
129
|
+
fileType,
|
|
130
|
+
};
|
|
131
|
+
const fileId = await ctx.runMutation(internal.files.internalUpsertFile, {
|
|
132
|
+
file: {
|
|
133
|
+
key,
|
|
134
|
+
url,
|
|
135
|
+
name,
|
|
136
|
+
size,
|
|
137
|
+
mimeType,
|
|
138
|
+
customId,
|
|
139
|
+
fileType,
|
|
140
|
+
},
|
|
141
|
+
userId,
|
|
142
|
+
options,
|
|
143
|
+
});
|
|
144
|
+
return { ok: true, fileId, hook: args.hook };
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=callbacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callbacks.js","sourceRoot":"","sources":["../../src/component/callbacks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const cleanupExpired: import("convex/server").RegisteredAction<"public", {
|
|
2
|
+
batchSize?: number | undefined;
|
|
3
|
+
dryRun?: boolean | undefined;
|
|
4
|
+
}, Promise<{
|
|
5
|
+
deletedCount: number;
|
|
6
|
+
keys: string[];
|
|
7
|
+
hasMore: boolean;
|
|
8
|
+
}>>;
|
|
9
|
+
//# sourceMappingURL=cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../../src/component/cleanup.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc;;;;kBAWT,MAAM;UACd,MAAM,EAAE;aACL,OAAO;GAsBlB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { action } from "./_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { internal } from "./_generated/api";
|
|
4
|
+
export const cleanupExpired = action({
|
|
5
|
+
args: {
|
|
6
|
+
batchSize: v.optional(v.number()),
|
|
7
|
+
dryRun: v.optional(v.boolean()),
|
|
8
|
+
},
|
|
9
|
+
returns: v.object({
|
|
10
|
+
deletedCount: v.number(),
|
|
11
|
+
keys: v.array(v.string()),
|
|
12
|
+
hasMore: v.boolean(),
|
|
13
|
+
}),
|
|
14
|
+
handler: async (ctx, args) => {
|
|
15
|
+
const globals = await ctx.runQuery(internal.config.getGlobalsInternal, {});
|
|
16
|
+
const limit = args.batchSize ?? globals.deleteBatchSize ?? 100;
|
|
17
|
+
const expired = (await ctx.runQuery(internal.queries.expiredBatch, {
|
|
18
|
+
now: Date.now(),
|
|
19
|
+
limit,
|
|
20
|
+
}));
|
|
21
|
+
const keys = expired.map((item) => item.key);
|
|
22
|
+
if (!args.dryRun && keys.length > 0) {
|
|
23
|
+
await ctx.runMutation(internal.files.deleteFilesByKey, { keys });
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
deletedCount: args.dryRun ? 0 : keys.length,
|
|
27
|
+
keys,
|
|
28
|
+
hasMore: expired.length >= limit,
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/component/cleanup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;IACnC,IAAI,EAAE;QACJ,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;KAChC;IACD,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC;QAChB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;QACxB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;KACrB,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAItB,EAAE;QACH,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;QAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,IAAK,OAAe,CAAC,eAAe,IAAI,GAAG,CAAC;QAExE,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE;YACjE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE;YACf,KAAK;SACN,CAAC,CAAmC,CAAC;QAEtC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAE7C,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,OAAO;YACL,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;YAC3C,IAAI;YACJ,OAAO,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;SACjC,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type Globals = {
|
|
2
|
+
uploadthingApiKey?: string;
|
|
3
|
+
defaultTtlMs?: number;
|
|
4
|
+
ttlByMimeType?: Record<string, number>;
|
|
5
|
+
ttlByFileType?: Record<string, number>;
|
|
6
|
+
deleteRemoteOnExpire?: boolean;
|
|
7
|
+
deleteBatchSize?: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function loadGlobals(ctx: {
|
|
10
|
+
db: any;
|
|
11
|
+
}): Promise<Globals>;
|
|
12
|
+
export declare const getGlobalsInternal: import("convex/server").RegisteredQuery<"internal", {}, Promise<Globals>>;
|
|
13
|
+
export declare function computeExpiresAt(params: {
|
|
14
|
+
now: number;
|
|
15
|
+
mimeType?: string;
|
|
16
|
+
fileType?: string;
|
|
17
|
+
expiresAt?: number;
|
|
18
|
+
ttlMs?: number;
|
|
19
|
+
globals?: Globals;
|
|
20
|
+
}): number | undefined;
|
|
21
|
+
export declare const setConfig: import("convex/server").RegisteredMutation<"public", {
|
|
22
|
+
replace?: boolean | undefined;
|
|
23
|
+
config: {
|
|
24
|
+
uploadthingApiKey?: string | undefined;
|
|
25
|
+
defaultTtlMs?: number | undefined;
|
|
26
|
+
ttlByMimeType?: Record<string, number> | undefined;
|
|
27
|
+
ttlByFileType?: Record<string, number> | undefined;
|
|
28
|
+
deleteRemoteOnExpire?: boolean | undefined;
|
|
29
|
+
deleteBatchSize?: number | undefined;
|
|
30
|
+
};
|
|
31
|
+
}, Promise<{
|
|
32
|
+
created: boolean;
|
|
33
|
+
}>>;
|
|
34
|
+
export declare const getConfig: import("convex/server").RegisteredQuery<"public", {}, Promise<{
|
|
35
|
+
defaultTtlMs: number | undefined;
|
|
36
|
+
ttlByMimeType: Record<string, number> | undefined;
|
|
37
|
+
ttlByFileType: Record<string, number> | undefined;
|
|
38
|
+
deleteRemoteOnExpire: boolean | undefined;
|
|
39
|
+
deleteBatchSize: number | undefined;
|
|
40
|
+
hasApiKey: boolean;
|
|
41
|
+
}>>;
|
|
42
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/component/config.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,OAAO,GAAG;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAoBF,wBAAsB,WAAW,CAAC,GAAG,EAAE;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAEpE;AAED,eAAO,MAAM,kBAAkB,2EAK7B,CAAC;AAEH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,MAAM,GAAG,SAAS,CAoBrB;AAED,eAAO,MAAM,SAAS;;;;;;;;;;;;GA0BpB,CAAC;AAEH,eAAO,MAAM,SAAS;;;;;;;GAapB,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { internalQuery, mutation, query } from "./_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { configUpdateValidator } from "./types";
|
|
4
|
+
const GLOBALS_ID = "globals";
|
|
5
|
+
async function readGlobals(db) {
|
|
6
|
+
const record = await db
|
|
7
|
+
.query("globals")
|
|
8
|
+
.withIndex("by_singleton", (q) => q.eq("singleton", GLOBALS_ID))
|
|
9
|
+
.unique();
|
|
10
|
+
if (!record)
|
|
11
|
+
return {};
|
|
12
|
+
return {
|
|
13
|
+
uploadthingApiKey: record.uploadthingApiKey,
|
|
14
|
+
defaultTtlMs: record.defaultTtlMs,
|
|
15
|
+
ttlByMimeType: record.ttlByMimeType,
|
|
16
|
+
ttlByFileType: record.ttlByFileType,
|
|
17
|
+
deleteRemoteOnExpire: record.deleteRemoteOnExpire,
|
|
18
|
+
deleteBatchSize: record.deleteBatchSize,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export async function loadGlobals(ctx) {
|
|
22
|
+
return await readGlobals(ctx.db);
|
|
23
|
+
}
|
|
24
|
+
export const getGlobalsInternal = internalQuery({
|
|
25
|
+
args: {},
|
|
26
|
+
handler: async (ctx) => {
|
|
27
|
+
return await readGlobals(ctx.db);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
export function computeExpiresAt(params) {
|
|
31
|
+
if (params.expiresAt !== undefined)
|
|
32
|
+
return params.expiresAt;
|
|
33
|
+
if (params.ttlMs !== undefined)
|
|
34
|
+
return params.now + params.ttlMs;
|
|
35
|
+
const globals = params.globals;
|
|
36
|
+
if (!globals)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (params.fileType && globals.ttlByFileType?.[params.fileType] !== undefined) {
|
|
39
|
+
return params.now + globals.ttlByFileType[params.fileType];
|
|
40
|
+
}
|
|
41
|
+
if (params.mimeType && globals.ttlByMimeType?.[params.mimeType] !== undefined) {
|
|
42
|
+
return params.now + globals.ttlByMimeType[params.mimeType];
|
|
43
|
+
}
|
|
44
|
+
if (globals.defaultTtlMs !== undefined) {
|
|
45
|
+
return params.now + globals.defaultTtlMs;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
export const setConfig = mutation({
|
|
50
|
+
args: {
|
|
51
|
+
config: configUpdateValidator,
|
|
52
|
+
replace: v.optional(v.boolean()),
|
|
53
|
+
},
|
|
54
|
+
handler: async (ctx, args) => {
|
|
55
|
+
const existing = await ctx.db
|
|
56
|
+
.query("globals")
|
|
57
|
+
.withIndex("by_singleton", (q) => q.eq("singleton", GLOBALS_ID))
|
|
58
|
+
.unique();
|
|
59
|
+
if (!existing) {
|
|
60
|
+
const record = { singleton: GLOBALS_ID, ...args.config };
|
|
61
|
+
await ctx.db.insert("globals", record);
|
|
62
|
+
return { created: true };
|
|
63
|
+
}
|
|
64
|
+
const update = args.replace
|
|
65
|
+
? { singleton: GLOBALS_ID, ...args.config }
|
|
66
|
+
: { ...existing, ...args.config };
|
|
67
|
+
delete update._id;
|
|
68
|
+
delete update._creationTime;
|
|
69
|
+
await ctx.db.patch(existing._id, update);
|
|
70
|
+
return { created: false };
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
export const getConfig = query({
|
|
74
|
+
args: {},
|
|
75
|
+
handler: async (ctx) => {
|
|
76
|
+
const globals = await loadGlobals(ctx);
|
|
77
|
+
return {
|
|
78
|
+
defaultTtlMs: globals.defaultTtlMs,
|
|
79
|
+
ttlByMimeType: globals.ttlByMimeType,
|
|
80
|
+
ttlByFileType: globals.ttlByFileType,
|
|
81
|
+
deleteRemoteOnExpire: globals.deleteRemoteOnExpire,
|
|
82
|
+
deleteBatchSize: globals.deleteBatchSize,
|
|
83
|
+
hasApiKey: Boolean(globals.uploadthingApiKey),
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/component/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convex.config.d.ts","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":";AACA,wBAA2D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convex.config.js","sourceRoot":"","sources":["../../src/component/convex.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,eAAe,eAAe,CAAC,0BAA0B,CAAC,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export declare const upsertFile: import("convex/server").RegisteredMutation<"public", {
|
|
2
|
+
options?: {
|
|
3
|
+
access?: {
|
|
4
|
+
allowUserIds?: string[] | undefined;
|
|
5
|
+
denyUserIds?: string[] | undefined;
|
|
6
|
+
visibility: "public" | "private" | "restricted";
|
|
7
|
+
} | undefined;
|
|
8
|
+
folder?: string | undefined;
|
|
9
|
+
fileType?: string | undefined;
|
|
10
|
+
tags?: string[] | undefined;
|
|
11
|
+
metadata?: any;
|
|
12
|
+
expiresAt?: number | undefined;
|
|
13
|
+
ttlMs?: number | undefined;
|
|
14
|
+
} | undefined;
|
|
15
|
+
file: {
|
|
16
|
+
uploadedAt?: number | undefined;
|
|
17
|
+
fileType?: string | undefined;
|
|
18
|
+
customId?: string | undefined;
|
|
19
|
+
key: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
url: string;
|
|
22
|
+
name: string;
|
|
23
|
+
size: number;
|
|
24
|
+
};
|
|
25
|
+
userId: string;
|
|
26
|
+
}, Promise<any>>;
|
|
27
|
+
export declare const internalUpsertFile: import("convex/server").RegisteredMutation<"internal", {
|
|
28
|
+
options?: {
|
|
29
|
+
access?: {
|
|
30
|
+
allowUserIds?: string[] | undefined;
|
|
31
|
+
denyUserIds?: string[] | undefined;
|
|
32
|
+
visibility: "public" | "private" | "restricted";
|
|
33
|
+
} | undefined;
|
|
34
|
+
folder?: string | undefined;
|
|
35
|
+
fileType?: string | undefined;
|
|
36
|
+
tags?: string[] | undefined;
|
|
37
|
+
metadata?: any;
|
|
38
|
+
expiresAt?: number | undefined;
|
|
39
|
+
ttlMs?: number | undefined;
|
|
40
|
+
} | undefined;
|
|
41
|
+
file: {
|
|
42
|
+
uploadedAt?: number | undefined;
|
|
43
|
+
fileType?: string | undefined;
|
|
44
|
+
customId?: string | undefined;
|
|
45
|
+
key: string;
|
|
46
|
+
mimeType: string;
|
|
47
|
+
url: string;
|
|
48
|
+
name: string;
|
|
49
|
+
size: number;
|
|
50
|
+
};
|
|
51
|
+
userId: string;
|
|
52
|
+
}, Promise<any>>;
|
|
53
|
+
export declare const setFileAccess: import("convex/server").RegisteredMutation<"public", {
|
|
54
|
+
access?: {
|
|
55
|
+
allowUserIds?: string[] | undefined;
|
|
56
|
+
denyUserIds?: string[] | undefined;
|
|
57
|
+
visibility: "public" | "private" | "restricted";
|
|
58
|
+
} | null | undefined;
|
|
59
|
+
key: string;
|
|
60
|
+
}, Promise<import("convex/values").GenericId<"files"> | null>>;
|
|
61
|
+
export declare const setFolderAccess: import("convex/server").RegisteredMutation<"public", {
|
|
62
|
+
access?: {
|
|
63
|
+
allowUserIds?: string[] | undefined;
|
|
64
|
+
denyUserIds?: string[] | undefined;
|
|
65
|
+
visibility: "public" | "private" | "restricted";
|
|
66
|
+
} | null | undefined;
|
|
67
|
+
folder: string;
|
|
68
|
+
}, Promise<import("convex/values").GenericId<"folderRules"> | null>>;
|
|
69
|
+
export declare const deleteFilesByKey: import("convex/server").RegisteredMutation<"internal", {
|
|
70
|
+
keys: string[];
|
|
71
|
+
}, Promise<void>>;
|
|
72
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/component/files.ts"],"names":[],"mappings":"AA8EA,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;gBAarB,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;gBAa7B,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;8DAmBxB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;oEAuC1B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;iBAe3B,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mutation, internalMutation } from "./_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { computeExpiresAt, loadGlobals } from "./config";
|
|
4
|
+
import { accessRuleValidator, fileInfoValidator, fileUpsertOptionsValidator } from "./types";
|
|
5
|
+
import { sanitizeAccessRule } from "./access";
|
|
6
|
+
async function upsertFileRecord(ctx, params) {
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
const globals = await loadGlobals(ctx);
|
|
9
|
+
const uploadedAt = params.file.uploadedAt ?? now;
|
|
10
|
+
const fileType = params.options?.fileType ?? params.file.fileType;
|
|
11
|
+
const expiresAt = computeExpiresAt({
|
|
12
|
+
now: uploadedAt,
|
|
13
|
+
mimeType: params.file.mimeType,
|
|
14
|
+
fileType,
|
|
15
|
+
expiresAt: params.options?.expiresAt,
|
|
16
|
+
ttlMs: params.options?.ttlMs,
|
|
17
|
+
globals,
|
|
18
|
+
});
|
|
19
|
+
const existing = await ctx.db
|
|
20
|
+
.query("files")
|
|
21
|
+
.withIndex("by_key", (q) => q.eq("key", params.file.key))
|
|
22
|
+
.unique();
|
|
23
|
+
const patch = {
|
|
24
|
+
url: params.file.url,
|
|
25
|
+
name: params.file.name,
|
|
26
|
+
size: params.file.size,
|
|
27
|
+
mimeType: params.file.mimeType,
|
|
28
|
+
uploadedAt,
|
|
29
|
+
userId: params.userId,
|
|
30
|
+
};
|
|
31
|
+
if (params.file.customId !== undefined)
|
|
32
|
+
patch.customId = params.file.customId;
|
|
33
|
+
if (fileType !== undefined)
|
|
34
|
+
patch.fileType = fileType;
|
|
35
|
+
if (params.options?.tags !== undefined)
|
|
36
|
+
patch.tags = params.options.tags;
|
|
37
|
+
if (params.options?.folder !== undefined)
|
|
38
|
+
patch.folder = params.options.folder;
|
|
39
|
+
if (params.options?.metadata !== undefined)
|
|
40
|
+
patch.metadata = params.options.metadata;
|
|
41
|
+
if (params.options?.access !== undefined) {
|
|
42
|
+
patch.access = sanitizeAccessRule(params.options.access);
|
|
43
|
+
}
|
|
44
|
+
if (expiresAt !== undefined)
|
|
45
|
+
patch.expiresAt = expiresAt;
|
|
46
|
+
if (existing) {
|
|
47
|
+
patch.replacedAt = now;
|
|
48
|
+
await ctx.db.patch(existing._id, patch);
|
|
49
|
+
return existing._id;
|
|
50
|
+
}
|
|
51
|
+
return await ctx.db.insert("files", {
|
|
52
|
+
key: params.file.key,
|
|
53
|
+
...patch,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export const upsertFile = mutation({
|
|
57
|
+
args: {
|
|
58
|
+
file: fileInfoValidator,
|
|
59
|
+
userId: v.string(),
|
|
60
|
+
options: v.optional(fileUpsertOptionsValidator),
|
|
61
|
+
},
|
|
62
|
+
handler: async (ctx, args) => {
|
|
63
|
+
return await upsertFileRecord(ctx, {
|
|
64
|
+
file: args.file,
|
|
65
|
+
userId: args.userId,
|
|
66
|
+
options: args.options,
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
export const internalUpsertFile = internalMutation({
|
|
71
|
+
args: {
|
|
72
|
+
file: fileInfoValidator,
|
|
73
|
+
userId: v.string(),
|
|
74
|
+
options: v.optional(fileUpsertOptionsValidator),
|
|
75
|
+
},
|
|
76
|
+
handler: async (ctx, args) => {
|
|
77
|
+
return await upsertFileRecord(ctx, {
|
|
78
|
+
file: args.file,
|
|
79
|
+
userId: args.userId,
|
|
80
|
+
options: args.options,
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
export const setFileAccess = mutation({
|
|
85
|
+
args: {
|
|
86
|
+
key: v.string(),
|
|
87
|
+
access: v.optional(v.union(accessRuleValidator, v.null())),
|
|
88
|
+
},
|
|
89
|
+
handler: async (ctx, args) => {
|
|
90
|
+
const existing = await ctx.db
|
|
91
|
+
.query("files")
|
|
92
|
+
.withIndex("by_key", (q) => q.eq("key", args.key))
|
|
93
|
+
.unique();
|
|
94
|
+
if (!existing)
|
|
95
|
+
return null;
|
|
96
|
+
await ctx.db.patch(existing._id, {
|
|
97
|
+
access: args.access === null ? undefined : sanitizeAccessRule(args.access),
|
|
98
|
+
});
|
|
99
|
+
return existing._id;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
export const setFolderAccess = mutation({
|
|
103
|
+
args: {
|
|
104
|
+
folder: v.string(),
|
|
105
|
+
access: v.optional(v.union(accessRuleValidator, v.null())),
|
|
106
|
+
},
|
|
107
|
+
handler: async (ctx, args) => {
|
|
108
|
+
const existing = await ctx.db
|
|
109
|
+
.query("folderRules")
|
|
110
|
+
.withIndex("by_folder", (q) => q.eq("folder", args.folder))
|
|
111
|
+
.unique();
|
|
112
|
+
if (args.access === null) {
|
|
113
|
+
if (existing) {
|
|
114
|
+
await ctx.db.delete(existing._id);
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const access = sanitizeAccessRule(args.access);
|
|
119
|
+
if (!access) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const updatedAt = Date.now();
|
|
123
|
+
if (existing) {
|
|
124
|
+
await ctx.db.patch(existing._id, {
|
|
125
|
+
access,
|
|
126
|
+
updatedAt,
|
|
127
|
+
});
|
|
128
|
+
return existing._id;
|
|
129
|
+
}
|
|
130
|
+
return await ctx.db.insert("folderRules", {
|
|
131
|
+
folder: args.folder,
|
|
132
|
+
access,
|
|
133
|
+
updatedAt,
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
export const deleteFilesByKey = internalMutation({
|
|
138
|
+
args: {
|
|
139
|
+
keys: v.array(v.string()),
|
|
140
|
+
},
|
|
141
|
+
handler: async (ctx, args) => {
|
|
142
|
+
for (const key of args.keys) {
|
|
143
|
+
const existing = await ctx.db
|
|
144
|
+
.query("files")
|
|
145
|
+
.withIndex("by_key", (q) => q.eq("key", key))
|
|
146
|
+
.unique();
|
|
147
|
+
if (existing) {
|
|
148
|
+
await ctx.db.delete(existing._id);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=files.js.map
|