upload
This commit is contained in:
commit
ed8c61ea95
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
.git
|
||||
.gitignore
|
||||
readme.md
|
||||
build
|
||||
.env
|
||||
./uploads
|
||||
./tmp
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
uploads/*
|
||||
tmp/*
|
||||
.env
|
||||
site
|
||||
site/*
|
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@ -0,0 +1,10 @@
|
||||
FROM denoland/deno:latest as base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . ./
|
||||
|
||||
RUN deno cache server.ts
|
||||
|
||||
CMD ["run", "--allow-net", "--allow-write", "--allow-read", "--allow-env", "server.ts"]
|
||||
|
19
fileTypes.ts
Normal file
19
fileTypes.ts
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
const allowedFiletypes = new Map()
|
||||
allowedFiletypes.set("image/jpeg", ".jpg")
|
||||
allowedFiletypes.set("image/png", ".png")
|
||||
allowedFiletypes.set("image/avif", ".avif")
|
||||
allowedFiletypes.set("image/gif", ".gif")
|
||||
allowedFiletypes.set("image/webp", ".webp")
|
||||
allowedFiletypes.set("image/apng", ".apng")
|
||||
allowedFiletypes.set("image/bmp", ".bmp")
|
||||
allowedFiletypes.set("image/svg+xml", ".svg")
|
||||
allowedFiletypes.set("image/tiff", ".tiff")
|
||||
allowedFiletypes.set("video/webm", ".webm")
|
||||
allowedFiletypes.set("video/mp4",".mp4")
|
||||
allowedFiletypes.set("video/mpeg",".mpeg")
|
||||
allowedFiletypes.set("video/x-msvideo", ".avi")
|
||||
allowedFiletypes.set("application/epub+zip", ".epub")
|
||||
allowedFiletypes.set("application/pdf", ".pdf")
|
||||
|
||||
export default allowedFiletypes
|
16
getFileHash.ts
Normal file
16
getFileHash.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { crypto, toHashString } from 'https://deno.land/std@0.176.0/crypto/mod.ts';
|
||||
|
||||
const getFileBuffer = (filePath: string) => {
|
||||
const file = Deno.openSync(filePath);
|
||||
const buf = new Uint8Array(file.statSync().size);
|
||||
file.readSync(buf);
|
||||
file.close();
|
||||
return buf;
|
||||
};
|
||||
|
||||
const getBuffer = (data: BufferSource) => toHashString(crypto.subtle.digestSync('SHA-256', data));
|
||||
|
||||
const getFileHash = (filePath: string) => getBuffer(getFileBuffer(filePath));
|
||||
|
||||
|
||||
export default getFileHash
|
10
mongodb.ts
Normal file
10
mongodb.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Bson, MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
|
||||
import "https://deno.land/std@0.178.0/dotenv/load.ts";
|
||||
|
||||
const client = new MongoClient();
|
||||
|
||||
await client.connect(Deno.env.get("MONGOURL"));
|
||||
|
||||
const db = client.database('hajabot')
|
||||
|
||||
export default db;
|
219
routes.ts
Normal file
219
routes.ts
Normal file
@ -0,0 +1,219 @@
|
||||
import { RouterContext } from "https://deno.land/x/oak/mod.ts"
|
||||
import "https://deno.land/std@0.178.0/dotenv/load.ts";
|
||||
import { writeFileSync, copy } from "https://deno.land/std/fs/mod.ts";
|
||||
import { renderFileToString } from "https://deno.land/x/dejs/mod.ts";
|
||||
|
||||
import getFileHash from './getFileHash.ts';
|
||||
import allowedFiletypes from './fileTypes.ts';
|
||||
import db from './mongodb.ts';
|
||||
const imageCollection = db.collection('images');
|
||||
const userTable = db.collection('users');
|
||||
|
||||
|
||||
|
||||
export const icons = async (ctx: RouterContext, next: any) => {
|
||||
const identifier = ctx.params.filename
|
||||
|
||||
console.log(["t_192.png", "t_48.png", "t_96.png"].indexOf(identifier))
|
||||
|
||||
if (["t_192.png", "t_48.png", "t_96.png"].indexOf(identifier) != -1){
|
||||
const img = await Deno.readFile(`${Deno.cwd()}/site/i/${ctx.params.filename}`);
|
||||
ctx.response.type = "image/png";
|
||||
ctx.response.body = img;
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export const announce = async (ctx: RouterContext) => {
|
||||
const body = await ctx.request.body({ type: 'json'}).value;
|
||||
|
||||
const count = await imageCollection.findOne({hash:body.hash});
|
||||
|
||||
if(body.size >= 50000000 && body.usercode == "anonymous"){
|
||||
ctx.response.status = 403
|
||||
}
|
||||
else if(count){
|
||||
ctx.response.body = {file: count.filename};
|
||||
ctx.response.status = 409
|
||||
}else{
|
||||
if(body.size >= 50000000){
|
||||
const users = await userTable.findOne({token:body.usercode});
|
||||
if(users){
|
||||
ctx.response.status = 200
|
||||
}else{
|
||||
ctx.response.status = 403
|
||||
}
|
||||
}else{
|
||||
ctx.response.status = 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const chunks = async (ctx: RouterContext) => {
|
||||
const hash = ctx.request.headers.get("file-hash");
|
||||
const number = ctx.request.headers.get("file-hash");
|
||||
|
||||
const body = await ctx.request.body({ type: 'form-data'});
|
||||
const formData = await body.value.read();
|
||||
|
||||
const size = ctx.request.headers.get("content-range").split("/")[1]
|
||||
const data = await Deno.readFile(formData.files[0].filename)
|
||||
|
||||
Deno.writeFileSync(`${Deno.env.get("TMPPATH")}${hash}.${ctx.request.headers.get("content-number")}.temp`, data)
|
||||
|
||||
ctx.response.status = 200
|
||||
}
|
||||
|
||||
export const mergeChunks = async (ctx: RouterContext) => {
|
||||
const body = await ctx.request.body({ type: 'json'}).value;
|
||||
|
||||
const ts = new Date();
|
||||
let newname = `${ts.valueOf()}${allowedFiletypes.get(body.type)}`
|
||||
|
||||
let files = []
|
||||
for await (const dirEntry of Deno.readDir(Deno.env.get("TMPPATH"))) {
|
||||
if(dirEntry.name.includes(body.hash) && dirEntry.isFile){
|
||||
files.push(dirEntry.name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const tempFile = await Deno.makeTempFile();
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let file = files.find(elem => elem.includes(`.${i}.temp`))
|
||||
const data = await Deno.readFileSync(`${Deno.env.get("TMPPATH")}${file}`)
|
||||
|
||||
await Deno.writeFileSync(tempFile, data, {"append": true})
|
||||
Deno.remove(`${Deno.env.get("TMPPATH")}${file}`);
|
||||
}
|
||||
|
||||
await copy(tempFile, `${Deno.env.get("IMAGEPATH")}${newname}`)
|
||||
|
||||
const id = await imageCollection.insertOne({
|
||||
hash:body.hash,
|
||||
mime:body.type,
|
||||
filename:newname,
|
||||
usercode:body.usercode,
|
||||
time:ts
|
||||
});
|
||||
|
||||
|
||||
const newhash = await getFileHash(tempFile)
|
||||
if(body.hash == newhash){
|
||||
ctx.response.body = {"file":newname}
|
||||
ctx.response.status = 201
|
||||
}else{
|
||||
ctx.response.status = 418
|
||||
}
|
||||
}
|
||||
|
||||
export const getImage = async (ctx: RouterContext) => {
|
||||
const identifier = ctx.params.filename;
|
||||
|
||||
const info = await imageCollection.findOne({filename:identifier});
|
||||
|
||||
if(!info){
|
||||
ctx.response.status = 404;
|
||||
ctx.response.body = {message: 'image does not exist'};
|
||||
return
|
||||
}else{
|
||||
const img = await Deno.readFile(`${Deno.env.get("IMAGEPATH")}${identifier}`);
|
||||
|
||||
ctx.response.type = info.mime;
|
||||
ctx.response.body = img;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const deleteImage = async (ctx: RouterContext) => {
|
||||
const identifier = ctx.params.filename;
|
||||
|
||||
const count = await imageCollection.findOne({filename:identifier});
|
||||
|
||||
if(!count){
|
||||
ctx.response.status = 404;
|
||||
ctx.response.body = {message: 'image does not exist'};
|
||||
return
|
||||
}else{
|
||||
console.log(count)
|
||||
|
||||
if(ctx.request.headers.get("usercode")==count.usercode){
|
||||
const deleted = await imageCollection.deleteOne({_id:count._id})
|
||||
if(!deleted){
|
||||
ctx.response.status = 500;
|
||||
}else{
|
||||
await Deno.remove(`${Deno.env.get("IMAGEPATH")}${count.filename}`);
|
||||
ctx.response.status = 204
|
||||
}
|
||||
|
||||
}else{
|
||||
ctx.response.body = {message: 'Unauthorized'};
|
||||
ctx.response.status = 401
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const uploadImage = async (ctx: RouterContext) => {
|
||||
const body = await ctx.request.body({ type: 'form-data'}).value;
|
||||
const formData = await body.read();
|
||||
|
||||
const usercode = ctx.request.headers.get("usercode") ? ctx.request.headers.get("usercode") : "anonymous";
|
||||
|
||||
let promises = [];
|
||||
let files = [];
|
||||
|
||||
formData.files.forEach(file => promises.push(new Promise(async (resolve, reject)=>{
|
||||
if(!allowedFiletypes.get(file.contentType)){
|
||||
reject("rejected filetype")
|
||||
}else{
|
||||
const timestamp = new Date()
|
||||
const hash = getFileHash(file.filename)
|
||||
|
||||
const count = await imageCollection.findOne({hash:hash});
|
||||
|
||||
if(!count){
|
||||
const newFilename = `${timestamp.valueOf()}${allowedFiletypes.get(file.contentType)}`
|
||||
copy(file.filename, `${Deno.env.get("IMAGEPATH")}${newFilename}`, {overwrite: true})
|
||||
const id = await imageCollection.insertOne({
|
||||
hash:hash,
|
||||
mime:file.contentType,
|
||||
filename:newFilename,
|
||||
usercode:usercode,
|
||||
time:timestamp
|
||||
});
|
||||
|
||||
files.push(newFilename)
|
||||
resolve(newFilename)
|
||||
}else{
|
||||
files.push(count.filename)
|
||||
resolve(count.filename)
|
||||
}
|
||||
}
|
||||
})))
|
||||
|
||||
const result = await Promise.allSettled(promises)
|
||||
|
||||
const rejected = await result.filter(obj => Object.keys(obj).some(key => obj[key].includes("rejected")))
|
||||
|
||||
if(rejected.length >= 1){
|
||||
ctx.response.status = 405;
|
||||
ctx.response.body = {message:rejected[0].reason}
|
||||
}else{
|
||||
if(files.length > 1){
|
||||
ctx.response.body = files
|
||||
}else{
|
||||
ctx.response.body = {"img":files[0]}
|
||||
}
|
||||
|
||||
ctx.response.status = 201
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
34
server.ts
Normal file
34
server.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
|
||||
import { getImage, uploadImage, deleteImage, chunks, mergeChunks, announce, icons} from './routes.ts';
|
||||
import "https://deno.land/std@0.178.0/dotenv/load.ts";
|
||||
|
||||
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', async(ctx: RouterContext, next: any) => {
|
||||
try {
|
||||
await ctx.send({
|
||||
root: `${Deno.cwd()}/site`,
|
||||
index: "index.html"
|
||||
})
|
||||
} catch {
|
||||
await next()
|
||||
}
|
||||
|
||||
})
|
||||
.get('/i/:filename', icons)
|
||||
.post('/upload', uploadImage)
|
||||
.post('/announce', announce)
|
||||
.post('/uploadchunk', chunks)
|
||||
.post('/finish', mergeChunks)
|
||||
.delete('/image/:filename', deleteImage)
|
||||
.get('/image/:filename', getImage)
|
||||
|
||||
|
||||
const app = new Application();
|
||||
|
||||
app.use(router.routes())
|
||||
app.use(router.allowedMethods());
|
||||
|
||||
app.listen({ port: Deno.env.get("PORT") });
|
||||
console.log(`Server is running on port ${Deno.env.get("PORT")}`)
|
Loading…
Reference in New Issue
Block a user