Skip to main content

File

The file model stores metadata about an uploaded file โ€” its name, MIME type, size, and a coarse type bucket (image / video / docs / other). The actual bytes are served from the static uploads folder (/uploads, see staticPath in the entry point); the file document is the reference that relations point at.

Files are used as avatars, as purchase-order attachments, and as tender documents.

// models/file.ts (definition, trimmed of the doc comment)
import { coreApp } from "../mod.ts";
import {
coerce, defaulted, enums, number, optional,
type RelationDataType, type RelationSortOrderType, string,
} from "lesan";
import { createUpdateAt } from "@lib";
import { user_excludes } from "./excludes.ts";

export const file_type_array = ["image", "video", "docs", "other"];
export const file_type_emums = enums(file_type_array);

export const file_pure = {
name: string(),
mimeType: string(),
size: number(),
type: defaulted(
coerce(file_type_emums, string(), (value) => value as typeof file_type_array[number]),
"other",
),
alt_text: optional(string()),
...createUpdateAt,
};

export const file_relations = {
uploader: {
schemaName: "user",
type: "single" as RelationDataType,
optional: true,
excludes: user_excludes,
relatedRelations: {
files: { type: "multiple" as RelationDataType, limit: 50, sort: { field: "_id", order: "desc" as RelationSortOrderType } },
},
},
};

Pure fieldsโ€‹

FieldTypeNotes
namestring()file name
mimeTypestring()e.g. application/pdf
sizenumber()bytes
typecoerce(enums([image, video, docs, other]), "other")a coarse bucket; the coerce lets clients send a raw string
alt_textoptional(string())accessibility text
createdAt / updatedAtspread from createUpdateAt

Note the coerce pattern used for enums throughout this app: the schema stores an enum (file_type_emums), but coerce accepts the incoming plain string() and casts it โ€” so a client sending "image" (a plain string) validates cleanly. This same idiom appears on unit, product, inventory, process, processStep, purchaseOrder, stepApproval, and tender.

Relationsโ€‹

RelationTargetTypeBack-reference
uploaderusersingle (optional)user.files

uploader records who uploaded the file. The back-reference user.files means a getMe / getUser projection can include the user's uploaded files without a second query.

Factoryโ€‹

export const files = () =>
coreApp.odm.newModel("file", file_pure, file_relations);

No createIndex, no model-level excludes โ€” file documents are returned as-is (minus what the relation-level excludes strips when a file is embedded inside another document).

In the workflowโ€‹

Purchase orders reference files through their attachments relation โ€” see the purchaseOrder model.

Run itโ€‹

curl -X POST http://localhost:1380/lesan \
-H "Content-Type: application/json" \
-H "token: <jwt>" \
-d '{
"service": "main",
"model": "file",
"act": "getFiles",
"details": {
"set": { "query": { "type": "docs" }, "page": 1 },
"get": { "name": true, "mimeType": true, "size": true, "uploader": { "first_name": true } }
}
}'

Errors & fixesโ€‹

ErrorCauseFix
file not foundthe _id given to getFile/removeFile doesn't existpass the real _id (from an insert or a getFiles response)
note

Runtime On npm/Bun import the framework from @hemedani/lesan; on Deno from jsr:@hemedani/lesan. The repo app itself uses the lesan path alias.