112 lines
2.9 KiB
TypeScript
112 lines
2.9 KiB
TypeScript
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
|
|
import * as fs from 'node:fs'
|
|
import * as path from 'node:path'
|
|
import { Type } from 'typebox'
|
|
import { GetAttachmentParamsSchema } from '../plugins/schemas/attachment.ts'
|
|
import { TypeboxRef } from '../utils/typebox-ref.ts'
|
|
|
|
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
|
const uploadDir = path.join(process.cwd(), 'uploads')
|
|
|
|
if (!fs.existsSync(uploadDir)) {
|
|
fs.mkdirSync(uploadDir, { recursive: true })
|
|
}
|
|
|
|
fastify.post(
|
|
'/attachment/upload',
|
|
{
|
|
schema: {
|
|
summary: 'Upload attachment',
|
|
tags: ['Attachment'],
|
|
operationId: 'attachment.upload',
|
|
description: 'Pass file to multipart/form-data',
|
|
consumes: ['multipart/form-data'],
|
|
response: {
|
|
200: Type.String({ format: 'uuid', description: 'Attachment UUID' }),
|
|
},
|
|
},
|
|
},
|
|
async (req, reply) => {
|
|
const user = req.user!
|
|
const data = await req.file()
|
|
|
|
if (!data) {
|
|
return reply.notAcceptable()
|
|
}
|
|
|
|
const meta = await fastify.prisma.attachment.create({
|
|
data: {
|
|
name: data.filename,
|
|
username: user.username,
|
|
mimetype: data.mimetype,
|
|
size: 0,
|
|
},
|
|
})
|
|
|
|
if (!meta) {
|
|
return reply.notAcceptable()
|
|
}
|
|
|
|
const filePath = path.join(process.cwd(), 'uploads', meta.id)
|
|
|
|
let fileSize = 0
|
|
|
|
await new Promise((resolve, reject) => {
|
|
const writeStream = fs.createWriteStream(filePath)
|
|
data.file.pipe(writeStream)
|
|
data.file.on('data', (chunk) => {
|
|
fileSize += chunk.length
|
|
})
|
|
data.file.on('end', resolve)
|
|
data.file.on('error', reject)
|
|
})
|
|
|
|
await fastify.prisma.attachment.update({
|
|
where: { id: meta.id },
|
|
data: { size: fileSize },
|
|
})
|
|
|
|
// await new Promise(resolve => setTimeout(resolve, Math.random() * 10000))
|
|
|
|
return meta.id
|
|
},
|
|
)
|
|
|
|
fastify.get(
|
|
'/attachment/:id',
|
|
{
|
|
schema: {
|
|
summary: 'Get attachment',
|
|
tags: ['Attachment'],
|
|
operationId: 'attachment.get',
|
|
params: TypeboxRef(GetAttachmentParamsSchema),
|
|
response: {
|
|
200: Type.Any({ description: 'Attachment content' }),
|
|
},
|
|
},
|
|
config: {
|
|
skipAuth: true,
|
|
},
|
|
},
|
|
async (req, reply) => {
|
|
const meta = await fastify.prisma.attachment.findFirst({
|
|
where: { id: req.params.id },
|
|
})
|
|
|
|
if (!meta) {
|
|
return reply.notFound('Attachment not found')
|
|
}
|
|
|
|
const filePath = path.join(process.cwd(), 'uploads', meta.id)
|
|
|
|
reply.type(meta.mimetype)
|
|
reply.header('Cache-Control', 'public, max-age=31536000')
|
|
reply.header('Content-Disposition', `inline; filename="${meta.name}"`)
|
|
|
|
return fs.createReadStream(filePath)
|
|
},
|
|
)
|
|
}
|
|
|
|
export default plugin
|