| 1 | import { extname } from 'node:path'; |
| 2 | |
| 3 | /** Where an attachment's bytes live, or the prefix holding all of a repository's. */ |
| 4 | export function attachmentKey(repositoryId |
| 5 | return `attachments/${repositoryId |
| 6 | } |
| 7 | |
| 8 | export const ATTACHMENT_MAX_BYTES = |
| 9 | |
| 10 | /** What may be attached, by extension. The type a file is served with comes from here, never from the uploader. SVG is left out: it can carry script. */ |
| 11 | const ATTACHMENT_TYPES: Record |
| 12 | '.png': 'image/png', |
| 13 | '.heic': 'image/heic', |
| 14 | '.jpg': 'image/jpeg', |
| 15 | '.jpeg': 'image/jpeg', |
| 16 | '.gif': 'image/gif', |
| 17 | '.webp': 'image/webp', |
| 18 | '.mp4': 'video/mp4', |
| 19 | '.webm': 'video/webm', |
| 20 | '.mov': 'video/quicktime', |
| 21 | '.pdf': 'application/pdf', |
| 22 | '.txt': 'text/plain', |
| 23 | '.log': 'text/plain', |
| 24 | '.md': 'text/markdown', |
| 25 | '.csv': 'text/csv', |
| 26 | '.json': 'application/json', |
| 27 | '.patch': 'text/x-diff', |
| 28 | '.diff': 'text/x-diff', |
| 29 | '.zip': 'application/zip', |
| 30 | '.gz': 'application/gzip', |
| 31 | '.tgz': 'application/gzip', |
| 32 | }; |
| 33 | |
| 34 | export const ATTACHMENT_EXTENSIONS = |
| 35 | |
| 36 | /** The media type `name` is served as, or undefined when its extension may not be attached. */ |
| 37 | export function attachmentTypeOf(name |
| 38 | return ATTACHMENT_TYPES[extname |
| 39 | } |
| 40 | |
| 41 | /** Images open in the browser, so an embedded one can be clicked through; anything else downloads. */ |
| 42 | export function isInlineAttachment(contentType |
| 43 | return contentType.startsWith |
| 44 | } |