This commit is contained in:
Hajamieli 2023-08-12 06:15:32 +03:00
commit ed8c61ea95
8 changed files with 320 additions and 0 deletions

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
.git
.gitignore
readme.md
build
.env
./uploads
./tmp

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
uploads/*
tmp/*
.env
site
site/*

10
Dockerfile Normal file
View 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
View 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
View 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
View 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
View 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
View 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")}`)