All chapters
Data
File uploads for an internal tool - scava.files
An app on Scava can store files that people upload: photos of receipts, PDFs, spreadsheets, scans. The app calls scava.files('name'). The files are stored by Scava, protected by the same sign-in and the same kind of rules as entries, and they count against the storage of the owner's plan. There is no bucket to set up.
The calls #
const receipts = scava.files('receipts'); // a collection of files; name rules like scava.db
const file = await receipts.upload(inputElement.files[0]);
// { id, name, type, size, created_by, created_at, url }
const files = await receipts.list(); // newest first
const one = await receipts.get(file.id);
const address = receipts.url(file.id); // for <img src> or <a href>
await receipts.remove(file.id);
uploadtakes aFilefrom<input type="file">or a drop, aBlob, or plain text. Give{ name: 'notes.txt' }as the second argument to name it.list()returns one page, newest first. When there are more files, the result has.next; pass it asafter, as withdb.list.{ order: 'oldest' }reverses the order. See Database.url(id)is the address of the file on the app's own address. It works in<img src>,<video>,<a href>and for downloads. Only signed-in people with access can open it.- The
typeof a file comes from its file ending, never from what the browser claims.
A complete example #
<input type="file" id="pick" accept="image/*">
<div id="gallery"></div>
<script>
const photos = scava.files('photos');
async function show() {
const gallery = document.getElementById('gallery');
gallery.textContent = '';
for (const f of await photos.list()) {
const img = document.createElement('img');
img.src = photos.url(f.id);
img.alt = f.name;
img.width = 160;
gallery.append(img);
}
}
document.getElementById('pick').addEventListener('change', async (event) => {
await photos.upload(event.target.files[0]);
show();
});
show();
</script>
Rules #
Files follow rules under files in scava.json, with the same values as for entries.
{
"files": {
"receipts": { "read": "own", "write": "own" },
"handbook": { "read": "all", "write": ["owner", "group:hr"] }
}
}
readdecides who sees the list and can download. With"own", a person sees only the files they uploaded.writedecides who may upload. A person with"own"may delete only their own files; with"all"or a group, anyone covered may delete any file in that collection.- Without a rule: read
all, writeown. The creator of the app can always do everything. - Files have no rules per field.
The rules are enforced on Scava's server on every request. See Permission rules.
What opens in the browser and what downloads #
A file you can open on the app's address could be an attack if it were allowed to run. So Scava shows only harmless types and downloads the rest.
| Opens in the browser | Always downloads |
|---|---|
| Images: PNG, JPEG, GIF, WebP, AVIF, BMP, ICO | HTML and SVG |
| PDF, in the browser's own viewer | Scripts and any unknown type |
| Audio: MP3, WAV, OGG, M4A | Office files such as DOCX, XLSX, PPTX |
| Video: MP4, WebM, MOV | JSON, XML, ZIP and everything else |
| Plain text and CSV |
Pictures, sound, video and text are served inside a sandbox that cannot run anything. Files that download are sent as binary attachments with the content type fixed, so an uploaded file can never become part of the app.
What is refused #
- Programs and installers, by file ending and by content (Windows, Linux and Mac programs and Java classes), even when renamed.
- Empty files.
- Files above the size limit.
- Names that do not pass Scava's text cleaning. File names are cleaned: path characters such as
/ \ : * ? " < > | #become-, invisible and direction-changing characters are removed, and names are shortened.
Limits #
| What | Limit |
|---|---|
| Size of one file | 25 MB |
| Files per app | 100,000 |
| Bytes per app | 50 GB |
| Changes per person and app | 300 in 10 minutes, uploads and deletions count like entries |
Files count against the storage of the owner's plan together with entries and the files of the app itself. If the storage is full, uploads fail with "This app has reached its storage limit."
In server code #
Server code uses env.SCAVA.files('receipts') with list, get, put(name, data, { type }), read(id) (which returns the details and the bytes in .data), remove and url. See Server code.
Export and deletion #
The owner's download of all data of an app includes all uploaded files. Deleting the app deletes its files.