| 1 | import * as GCP from "alchemy/GCP"; |
| 2 | |
| 3 | /** Only objects under this prefix trigger the indexer. */ |
| 4 | export const UPLOADS_PREFIX = "uploads/"; |
| 5 | |
| 6 | /** |
| 7 | * Where the uploaded bytes live. `forceDestroy` empties the bucket on |
| 8 | * `alchemy destroy`; drop it in production so a destroy cannot take user |
| 9 | * data with it. |
| 10 | */ |
| 11 | export const Uploads = GCP.Storage.Bucket("Uploads", { |
| 12 | forceDestroy: true, |
| 13 | }); |
| 14 | |
| 15 | /** |
| 16 | * One Firestore document per file, at `files/{name}`, written by the |
| 17 | * indexer once the object has landed. |
| 18 | */ |
| 19 | export const Files = GCP.Firestore.Database("Files", { |
| 20 | type: "FIRESTORE_NATIVE", |
| 21 | }); |
| 22 | |
| 23 | /** |
| 24 | * File names double as Firestore document ids and object-name suffixes, |
| 25 | * so keep them to a URL- and id-safe alphabet. |
| 26 | */ |
| 27 | export const isValidName = (name: string) => |
| 28 | /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(name); |
| 29 | |
| 30 | export const objectFor = (name: string) => `${UPLOADS_PREFIX}${name}`; |
| 31 | |
| 32 | export const documentFor = (name: string) => `files/${name}`; |
| 33 | |
| 34 | /** What the indexer records for each upload. */ |
| 35 | export interface FileRecord { |
| 36 | name: string; |
| 37 | object: string; |
| 38 | generation: string; |
| 39 | size: number; |
| 40 | contentType: string; |
| 41 | sha256: string; |
| 42 | indexedAt: Date; |
| 43 | } |