@arcote.tech/arc-files 0.7.26 → 0.7.28
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/package.json +4 -4
- package/src/aggregates/file.ts +38 -5
- package/src/file-type-builder.ts +23 -6
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-files",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.28",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Generic file upload fragment for Arc framework — S3 presigned upload + provider file_id binding",
|
|
7
7
|
"main": "./src/index.ts",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"type-check": "tsc --noEmit"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"@arcote.tech/arc": "^0.7.
|
|
14
|
-
"@arcote.tech/arc-ds": "^0.7.
|
|
15
|
-
"@arcote.tech/platform": "^0.7.
|
|
13
|
+
"@arcote.tech/arc": "^0.7.28",
|
|
14
|
+
"@arcote.tech/arc-ds": "^0.7.28",
|
|
15
|
+
"@arcote.tech/platform": "^0.7.28",
|
|
16
16
|
"@aws-sdk/client-s3": "^3.658.0",
|
|
17
17
|
"@aws-sdk/s3-request-presigner": "^3.658.0",
|
|
18
18
|
"lucide-react": ">=0.400.0",
|
package/src/aggregates/file.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
string,
|
|
9
9
|
type ArcAggregateElement,
|
|
10
10
|
type ArcId,
|
|
11
|
+
type ArcRawShape,
|
|
11
12
|
} from "@arcote.tech/arc";
|
|
12
13
|
import type { S3Bridge } from "../s3/s3-client";
|
|
13
14
|
import type { FileProviderName } from "../types";
|
|
@@ -51,9 +52,20 @@ export function createFileTypeAggregate<const Data extends FileTypeData>(
|
|
|
51
52
|
const metadataShape = data.metadata;
|
|
52
53
|
const fullShape = mergeUnsafe(builtInFields, metadataShape);
|
|
53
54
|
|
|
54
|
-
// `requestUpload` params
|
|
55
|
-
//
|
|
56
|
-
//
|
|
55
|
+
// `requestUpload` params = built-in (name, mime, size) + custom metadata,
|
|
56
|
+
// ale metadata są OPCJONALNE. Pola scope (accountId itd.) są wyprowadzane
|
|
57
|
+
// autorytatywnie z aktywnego tokena server-side (patrz handler niżej), więc
|
|
58
|
+
// klient nie musi — i nie powinien — ich wysyłać. Consumer może pominąć
|
|
59
|
+
// wartość pochodzącą z tokena; serwer i tak ją nadpisze wartością z tokena.
|
|
60
|
+
const optionalMetadata: ArcRawShape = {};
|
|
61
|
+
for (const [key, element] of Object.entries(metadataShape)) {
|
|
62
|
+
// `validate(undefined) === false` → element już jest opcjonalny
|
|
63
|
+
// (ArcOptional), nie owijaj ponownie (ArcOptional nie ma `.optional()`).
|
|
64
|
+
const alreadyOptional = (element as any).validate?.(undefined) === false;
|
|
65
|
+
optionalMetadata[key] = alreadyOptional
|
|
66
|
+
? element
|
|
67
|
+
: (element as any).optional();
|
|
68
|
+
}
|
|
57
69
|
const requestUploadParams = mergeUnsafe(
|
|
58
70
|
{
|
|
59
71
|
_id: fileId,
|
|
@@ -61,7 +73,7 @@ export function createFileTypeAggregate<const Data extends FileTypeData>(
|
|
|
61
73
|
mime: string(),
|
|
62
74
|
size: number(),
|
|
63
75
|
},
|
|
64
|
-
|
|
76
|
+
optionalMetadata,
|
|
65
77
|
);
|
|
66
78
|
|
|
67
79
|
// `fileRequested` event payload = `requestUploadParams` + s3 fields
|
|
@@ -142,6 +154,27 @@ export function createFileTypeAggregate<const Data extends FileTypeData>(
|
|
|
142
154
|
const s3Key = `${data.name}/${p._id}/${p.name}`;
|
|
143
155
|
const uploadUrl = await s3.presignUpload(s3Key, p.name && p.mime);
|
|
144
156
|
|
|
157
|
+
// Pola scope (accountId itd.) bierzemy AUTORYTATYWNIE z aktywnego
|
|
158
|
+
// tokena — nigdy z params klienta. `protection.check(tokenPayload)`
|
|
159
|
+
// zwraca dokładnie mapowanie token→metadata (`{accountId: p.accountId}`),
|
|
160
|
+
// to samo, którego arc-core używa do restrykcji scope. Zapobiega
|
|
161
|
+
// podszyciu pod cudze accountId i znosi wymóg wysyłania go przez
|
|
162
|
+
// klienta (który przed załadowaniem `me` słał undefined → padała
|
|
163
|
+
// walidacja wejścia).
|
|
164
|
+
const auth = (ctx as any).$auth as
|
|
165
|
+
| { params: any; tokenName: string }
|
|
166
|
+
| undefined;
|
|
167
|
+
const derived: Record<string, any> = {};
|
|
168
|
+
if (auth?.tokenName) {
|
|
169
|
+
for (const protection of data.protections) {
|
|
170
|
+
if ((protection.token as any).name !== auth.tokenName) continue;
|
|
171
|
+
const restriction = protection.check(auth.params);
|
|
172
|
+
if (restriction && typeof restriction === "object") {
|
|
173
|
+
Object.assign(derived, restriction);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
145
178
|
const payload: Record<string, any> = {
|
|
146
179
|
_id: p._id,
|
|
147
180
|
name: p.name,
|
|
@@ -151,7 +184,7 @@ export function createFileTypeAggregate<const Data extends FileTypeData>(
|
|
|
151
184
|
s3Key,
|
|
152
185
|
};
|
|
153
186
|
for (const key of Object.keys(metadataShape)) {
|
|
154
|
-
payload[key] = p[key];
|
|
187
|
+
payload[key] = key in derived ? derived[key] : p[key];
|
|
155
188
|
}
|
|
156
189
|
await (ctx as any).fileRequested.emit(payload);
|
|
157
190
|
|
package/src/file-type-builder.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import type {
|
|
3
3
|
ArcId,
|
|
4
4
|
ArcRawShape,
|
|
5
|
+
ArcRawShapeType,
|
|
5
6
|
ArcTokenAny,
|
|
6
7
|
Merge,
|
|
8
|
+
ViewProtectionFn,
|
|
7
9
|
} from "@arcote.tech/arc";
|
|
8
10
|
import { createFileTypeAggregate } from "./aggregates/file";
|
|
9
11
|
import type { S3Bridge } from "./s3/s3-client";
|
|
@@ -17,9 +19,24 @@ export interface FileTypeProtection {
|
|
|
17
19
|
token: ArcTokenAny;
|
|
18
20
|
// `params` is the decoded token payload; user-provided check returns
|
|
19
21
|
// a where-clause restriction (`{field: value}`) or `false` to deny.
|
|
20
|
-
|
|
22
|
+
// Typowane tym samym `ViewProtectionFn` co `aggregate.protectBy` — payload
|
|
23
|
+
// tokena inferowany z `token` (zero `any` po stronie konsumenta).
|
|
24
|
+
check: ViewProtectionFn;
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Ctx handlera custom mutation (`.withMutation()`). Auto-generowany emitter
|
|
29
|
+
* `<name>Performed` jest TYPOWANY (łapie literówkę w nazwie eventu). Reszta
|
|
30
|
+
* ctx (`modify`/`$query`/`set`/`findOne`) pozostaje permisywna — pełne
|
|
31
|
+
* otypowanie blokuje limit instancjacji TS na rosnących tuple
|
|
32
|
+
* Events/MutateMethods (ten sam powód co `aggAny: any` w `aggregates/file.ts`).
|
|
33
|
+
*/
|
|
34
|
+
export type FileMutationContext<N extends string> = {
|
|
35
|
+
[K in `${N}Performed`]: {
|
|
36
|
+
emit: (payload: Record<string, unknown>) => Promise<void>;
|
|
37
|
+
};
|
|
38
|
+
} & Record<string, any>;
|
|
39
|
+
|
|
23
40
|
/**
|
|
24
41
|
* Custom mutation rejestrowana przez `.withMutation()`. Builder generuje
|
|
25
42
|
* dedicated event `<name>Performed` + mutation method z params. Handler
|
|
@@ -95,10 +112,7 @@ export class ArcFileTypeBuilder<
|
|
|
95
112
|
* tokena. Każdy `.protectedBy()` dodaje jeden entry do listy; przy
|
|
96
113
|
* `.build()` builder iteruje i woła `aggregate.protectBy()` per entry.
|
|
97
114
|
*/
|
|
98
|
-
protectedBy<T extends ArcTokenAny>(
|
|
99
|
-
token: T,
|
|
100
|
-
check: (params: any) => Record<string, unknown> | false,
|
|
101
|
-
) {
|
|
115
|
+
protectedBy<T extends ArcTokenAny>(token: T, check: ViewProtectionFn<T>) {
|
|
102
116
|
type NewProtections = [
|
|
103
117
|
...Data["protections"],
|
|
104
118
|
{ token: T; check: typeof check },
|
|
@@ -126,7 +140,10 @@ export class ArcFileTypeBuilder<
|
|
|
126
140
|
withMutation<const N extends string, const P extends ArcRawShape>(
|
|
127
141
|
name: N,
|
|
128
142
|
params: P,
|
|
129
|
-
handler: (
|
|
143
|
+
handler: (
|
|
144
|
+
ctx: FileMutationContext<N>,
|
|
145
|
+
params: { _id: string } & ArcRawShapeType<P>,
|
|
146
|
+
) => Promise<void>,
|
|
130
147
|
) {
|
|
131
148
|
type NewMutations = [
|
|
132
149
|
...Data["mutations"],
|