feat: continue work

Signed-off-by: Avior <github@avior.me>
This commit is contained in:
Florian Bouillon 2023-06-28 01:15:56 +02:00
parent d76f412b82
commit 9530be5c43
16 changed files with 148 additions and 86 deletions

22
.dockerignore Normal file
View File

@ -0,0 +1,22 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# macOS-specific files
.DS_Store
slicers/*
# Coverage
coverage/

View File

@ -3,7 +3,7 @@
######### #########
# Build # # Build #
######### #########
FROM node:alpine as BUILD_IMAGE FROM node:20-alpine as BUILD_IMAGE
# External deps (for node-gyp add: "python3 make g++") # External deps (for node-gyp add: "python3 make g++")
# git is used to install the npm packages with git deps # git is used to install the npm packages with git deps
@ -32,12 +32,11 @@ RUN npm prune --omit=dev
############## ##############
# Production # # Production #
############## ##############
FROM node:latest as PROD_IMAGE FROM node:20-slim as PROD_IMAGE
# inform software to be in production # inform software to be in production
ENV NODE_ENV=production ENV NODE_ENV=production
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV CONFIGS_PATH=./configs
# Download & Install Slic3r # Download & Install Slic3r
# RUN apt-get update \ # RUN apt-get update \
@ -47,13 +46,13 @@ ENV CONFIGS_PATH=./configs
# ENV SLICER_PATH slic3r # ENV SLICER_PATH slic3r
# Download & install PrusaSlicer # Download & install PrusaSlicer
ADD https://github.com/prusa3d/PrusaSlicer/releases/download/version_2.6.0/PrusaSlicer-2.6.0+linux-x64-GTK3-202306191220.tar.bz2 ./
RUN tar -xvf PrusaSlicer-2.6.0+linux-x64-GTK3-202306191220.tar.bz2 -C /opt
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
prusa-slicer \ prusa-slicer bzip2 \
&& apt-get remove prusa-slicer -y \ && apt-get remove prusa-slicer -y \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
ADD https://github.com/prusa3d/PrusaSlicer/releases/download/version_2.6.0/PrusaSlicer-2.6.0+linux-x64-GTK3-202306191220.tar.bz2 ./
RUN tar -xvf PrusaSlicer-2.6.0+linux-x64-GTK3-202306191220.tar.bz2 -C /opt
ENV PATH /opt/PrusaSlicer-2.6.0+linux-x64-GTK3-202306191220/bin:$PATH ENV PATH /opt/PrusaSlicer-2.6.0+linux-x64-GTK3-202306191220/bin:$PATH
ENV SLICER_PATH prusa-slicer ENV SLICER_PATH prusa-slicer
@ -71,7 +70,6 @@ WORKDIR /home/node
# copy from build image # copy from build image
COPY --chown=node:node --from=BUILD_IMAGE /home/node/node_modules ./node_modules COPY --chown=node:node --from=BUILD_IMAGE /home/node/node_modules ./node_modules
COPY --chown=node:node --from=BUILD_IMAGE /home/node/configs ./configs
COPY --chown=node:node --from=BUILD_IMAGE /home/node/dist ./dist COPY --chown=node:node --from=BUILD_IMAGE /home/node/dist ./dist
COPY --chown=node:node --from=BUILD_IMAGE /home/node/package.json /home/node/.env* ./ COPY --chown=node:node --from=BUILD_IMAGE /home/node/package.json /home/node/.env* ./

10
package-lock.json generated
View File

@ -12,7 +12,7 @@
"@astrojs/tailwind": "^3.1.3", "@astrojs/tailwind": "^3.1.3",
"@dzeio/logger": "^3.0.0", "@dzeio/logger": "^3.0.0",
"@dzeio/object-util": "^1.5.0", "@dzeio/object-util": "^1.5.0",
"@dzeio/url-manager": "^1.0.9", "@dzeio/url-manager": "^1.0.10",
"astro": "^2.6.4", "astro": "^2.6.4",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
@ -604,11 +604,11 @@
"integrity": "sha512-NlKJulgd95Gvca3Wx9BQi0rABzwtvuCe9JDPBe9lOVmDDs2ufEWiTsLq59rtE1BEuuGzIm9wj/+8Imgk1Svfrw==" "integrity": "sha512-NlKJulgd95Gvca3Wx9BQi0rABzwtvuCe9JDPBe9lOVmDDs2ufEWiTsLq59rtE1BEuuGzIm9wj/+8Imgk1Svfrw=="
}, },
"node_modules/@dzeio/url-manager": { "node_modules/@dzeio/url-manager": {
"version": "1.0.9", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/@dzeio/url-manager/-/url-manager-1.0.9.tgz", "resolved": "https://registry.npmjs.org/@dzeio/url-manager/-/url-manager-1.0.10.tgz",
"integrity": "sha512-aBERzF4xznhQhHlzv35fp2cl99N+VjM37CL+R5DJiyIJmgmiz56zru1SKq8Xnv2rulF+6QrrnlT6ZulQDdp9EA==", "integrity": "sha512-gy8/0yp60crudcqIwsyFsfbHOnG32TAYg14m+at9wzw/bau5URsarlLkO5A3tigk91m05tZQfHLa4xwrXBSY/w==",
"dependencies": { "dependencies": {
"@dzeio/object-util": "^1.4.0" "@dzeio/object-util": "^1.5.0"
} }
}, },
"node_modules/@emmetio/abbreviation": { "node_modules/@emmetio/abbreviation": {

View File

@ -16,7 +16,7 @@
"@astrojs/tailwind": "^3.1.3", "@astrojs/tailwind": "^3.1.3",
"@dzeio/logger": "^3.0.0", "@dzeio/logger": "^3.0.0",
"@dzeio/object-util": "^1.5.0", "@dzeio/object-util": "^1.5.0",
"@dzeio/url-manager": "^1.0.9", "@dzeio/url-manager": "^1.0.10",
"astro": "^2.6.4", "astro": "^2.6.4",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",

11
src/env.d.ts vendored
View File

@ -1,6 +1,5 @@
/// <reference path="../.astro/types.d.ts" /> /// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" /> /// <reference types="astro/client" />
/// <reference types="./libs/ResponseBuilder" />
interface ImportMetaEnv { interface ImportMetaEnv {
PRUSASLICER_PATH?: string PRUSASLICER_PATH?: string
@ -21,6 +20,14 @@ declare namespace App {
* authentification key is the api key or the session token * authentification key is the api key or the session token
*/ */
authKey?: string authKey?: string
responseBuilder?: ResponseBuilder responseBuilder: {
body(body: string | Buffer | object | null | undefined): this
headers(headers: HeadersInit ): this
addHeader(key: string, value: string): this
addHeaders(headers: Record<string, string>): this
removeHeader(key: string): this
status(status: number): this
build(): Response
} // ./libs/ResponseBuilder object
} }
} }

View File

@ -24,7 +24,7 @@ export default class RateLimiter {
/** /**
* timeSpan in seconds * timeSpan in seconds
*/ */
public static timeSpan = 600 public static timeSpan = 60
private static instance: RateLimiter = new RateLimiter() private static instance: RateLimiter = new RateLimiter()
public static getInstance(): RateLimiter { public static getInstance(): RateLimiter {

View File

@ -6,10 +6,12 @@ import { objectLoop } from '@dzeio/object-util'
export default class ResponseBuilder { export default class ResponseBuilder {
private _body: BodyInit | null | undefined private _body: BodyInit | null | undefined
public body(body: string | object | null | undefined) { public body(body: string | Buffer | object | null | undefined) {
if (typeof body === 'object') { if (typeof body === 'object' && !(body instanceof Buffer)) {
this._body = JSON.stringify(body) this._body = JSON.stringify(body)
this.addHeader('Content-Type', 'application/json') this.addHeader('Content-Type', 'application/json')
} else if (body instanceof Buffer) {
this._body = body.toString()
} else { } else {
this._body = body this._body = body
} }

View File

@ -2,7 +2,7 @@ import DaoFactory from '../models/DaoFactory'
import CookieManager from './CookieManager' import CookieManager from './CookieManager'
import { buildRFC7807 } from './RFCs/RFC7807' import { buildRFC7807 } from './RFCs/RFC7807'
interface Permission { export interface Permission {
name: string name: string
/** /**
* if set it will be usable by users * if set it will be usable by users

View File

@ -1,16 +1,43 @@
import { objectLoop } from '@dzeio/object-util'
import URLManager from '@dzeio/url-manager'
import { defineMiddleware } from "astro/middleware" import { defineMiddleware } from "astro/middleware"
import { validateAuth } from '../libs/validateAuth' import { buildRFC7807 } from '../libs/RFCs/RFC7807'
import { Permission, validateAuth } from '../libs/validateAuth'
const endpointsPermissions: Record<string, Permission> = {
'/api/v1/users/[userId]/configs/[configId]/files/[fileName]': {
api: true,
cookie: true,
name: 'configs.get'
}
}
function objectFind(obj: object, fn: (value: any, key: any) => boolean): {key: string, value: any} | null {
let res: {key: string, value: any} | null = null
objectLoop(obj, (value, key) => {
const tmp = fn(value, key)
if (tmp) {
res = {
key, value
}
}
return tmp
})
return res
}
// `context` and `next` are automatically typed // `context` and `next` are automatically typed
export default defineMiddleware(async (context, next) => { export default defineMiddleware(async (context, next) => {
if (!context.request.url.includes('api')) { if (!context.request.url.includes('api')) {
return next() return next()
} }
const auth = await validateAuth(context.request, { const permission = objectFind(endpointsPermissions, (_, key) => new URLManager(key).toString(context.params as any) === context.url.pathname)
name: 'slicing.slice', if (!permission) {
api: true, return buildRFC7807({
cookie: true type: 'idk'
}) })
}
const auth = await validateAuth(context.request, permission.value)
if (typeof auth === 'object') { if (typeof auth === 'object') {
return auth return auth
} }

View File

@ -1,9 +1,21 @@
import { defineMiddleware } from "astro/middleware" import { defineMiddleware } from "astro/middleware"
import { buildRFC7807 } from '../libs/RFCs/RFC7807'
import ResponseBuilder from '../libs/ResponseBuilder' import ResponseBuilder from '../libs/ResponseBuilder'
// `context` and `next` are automatically typed // `context` and `next` are automatically typed
export default defineMiddleware(async (context, next) => { export default defineMiddleware(async ({ request, locals }, next) => {
context.locals.responseBuilder = new ResponseBuilder() locals.responseBuilder = new ResponseBuilder()
console.log(`[${new Date().toISOString()}] ${request.headers.get('user-agent')?.slice(0, 32).padEnd(32)} ${request.method.padEnd(7)} ${request.url}`)
return next() try {
const res = await next()
console.log(`[${new Date().toISOString()}] ${request.headers.get('user-agent')?.slice(0, 32).padEnd(32)} ${request.method.padEnd(7)} ${res.status} ${request.url}`)
return res
} catch (e) {
console.error(e)
return buildRFC7807({
type: '/docs/errors/global-error',
status: 500
})
}
}) })

View File

@ -1,25 +0,0 @@
import type { APIRoute } from 'astro'
import crypto from 'node:crypto'
import { validateAuth } from '../../../../../libs/validateAuth'
import DaoFactory from '../../../../../models/DaoFactory'
export const post: APIRoute = async ({ params, request }) => {
validateAuth(request, {
name: 'keys.create',
cookie: true,
api: false
})
const userId = params.userId as string
const dao = await DaoFactory.get('apiKey').create({
user: userId,
key: crypto.randomUUID(),
permissions: [
'admin.user.list'
]
})
return {
status: 201,
body: JSON.stringify(dao)
}
}

View File

@ -1,17 +0,0 @@
import type { APIRoute } from 'astro'
import { validateAuth } from '../../../libs/validateAuth'
export const get: APIRoute = async ({ params, request }) => {
const requestInvalid = await validateAuth(request, {
name: 'user.list',
api: false,
cookie: true
})
if (requestInvalid) {
return requestInvalid
}
return {
status: 200,
body: JSON.stringify({iam: true})
}
}

View File

@ -1,10 +1,8 @@
import { objectOmit } from '@dzeio/object-util'
import type { APIRoute } from 'astro' import type { APIRoute } from 'astro'
import { buildRFC7807 } from '../../../../../../../libs/RFCs/RFC7807' import { buildRFC7807 } from '../../../../../../../../libs/RFCs/RFC7807'
import DaoFactory from '../../../../../../../models/DaoFactory' import DaoFactory from '../../../../../../../../models/DaoFactory'
export const get: APIRoute = async ({ params, request }) => { export const get: APIRoute = async ({ params, locals }) => {
const userId = params.userId as string
const configId = params.configId as string const configId = params.configId as string
const fileName = params.fileName as string const fileName = params.fileName as string
@ -19,8 +17,8 @@ export const get: APIRoute = async ({ params, request }) => {
const file = dao.files.find((it) => it.name === fileName) const file = dao.files.find((it) => it.name === fileName)
return { return locals.responseBuilder
status: 200, .status(200)
body: file?.data .body(file?.data)
} .build()
} }

View File

@ -1,9 +1,10 @@
import { objectOmit } from '@dzeio/object-util' import { objectOmit } from '@dzeio/object-util'
import type { APIRoute } from 'astro' import type { APIRoute } from 'astro'
import { buildRFC7807 } from '../../../../../libs/RFCs/RFC7807' import { buildRFC7807 } from '../../../../../../libs/RFCs/RFC7807'
import DaoFactory from '../../../../../models/DaoFactory' import StatusCode from '../../../../../../libs/StatusCode'
import DaoFactory from '../../../../../../models/DaoFactory'
export const post: APIRoute = async ({ params, request }) => { export const post: APIRoute = async ({ params, request, locals }) => {
const userId = params.userId as string const userId = params.userId as string
const body = request.body const body = request.body
@ -37,8 +38,8 @@ export const post: APIRoute = async ({ params, request }) => {
data: buffer data: buffer
}] }]
}) })
return { return locals.responseBuilder
status: 201, .status(StatusCode.CREATED)
body: JSON.stringify(objectOmit(dao ?? {}, 'files')) .body(objectOmit(dao ?? {}, 'files'))
} .build()
} }

View File

@ -0,0 +1,20 @@
import type { APIRoute } from 'astro'
import crypto from 'node:crypto'
import StatusCode from '../../../../../../libs/StatusCode'
import DaoFactory from '../../../../../../models/DaoFactory'
export const post: APIRoute = async ({ params, locals }) => {
const userId = params.userId as string
const dao = await DaoFactory.get('apiKey').create({
user: userId,
key: crypto.randomUUID(),
permissions: [
'admin.user.list'
]
})
return locals.responseBuilder
.status(StatusCode.CREATED)
.body(dao)
.build()
}

View File

@ -0,0 +1,17 @@
import type { APIRoute } from 'astro'
import { buildRFC7807 } from '../../../../libs/RFCs/RFC7807'
import StatusCode from '../../../../libs/StatusCode'
export const get: APIRoute = async ({ locals }) => {
return locals.responseBuilder
.status(200)
.body({iam: true})
.build()
}
export const options: APIRoute = async () => {
return buildRFC7807({
status: StatusCode.METHOD_NOT_ALLOWED,
details: 'Allowed methods: "GET"'
})
}