diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..7fc1766
--- /dev/null
+++ b/.dockerignore
@@ -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/
diff --git a/Dockerfile b/Dockerfile
index 00242bc..c3724de 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@
#########
# Build #
#########
-FROM node:alpine as BUILD_IMAGE
+FROM node:20-alpine as BUILD_IMAGE
# External deps (for node-gyp add: "python3 make g++")
# git is used to install the npm packages with git deps
@@ -32,12 +32,11 @@ RUN npm prune --omit=dev
##############
# Production #
##############
-FROM node:latest as PROD_IMAGE
+FROM node:20-slim as PROD_IMAGE
# inform software to be in production
ENV NODE_ENV=production
ENV HOST=0.0.0.0
-ENV CONFIGS_PATH=./configs
# Download & Install Slic3r
# RUN apt-get update \
@@ -47,13 +46,13 @@ ENV CONFIGS_PATH=./configs
# ENV SLICER_PATH slic3r
# 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 \
&& apt-get install -y --no-install-recommends \
- prusa-slicer \
+ prusa-slicer bzip2 \
&& apt-get remove prusa-slicer -y \
&& 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 SLICER_PATH prusa-slicer
@@ -71,7 +70,6 @@ WORKDIR /home/node
# 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/configs ./configs
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* ./
diff --git a/package-lock.json b/package-lock.json
index 705927e..8e9c85b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
"@astrojs/tailwind": "^3.1.3",
"@dzeio/logger": "^3.0.0",
"@dzeio/object-util": "^1.5.0",
- "@dzeio/url-manager": "^1.0.9",
+ "@dzeio/url-manager": "^1.0.10",
"astro": "^2.6.4",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0",
@@ -604,11 +604,11 @@
"integrity": "sha512-NlKJulgd95Gvca3Wx9BQi0rABzwtvuCe9JDPBe9lOVmDDs2ufEWiTsLq59rtE1BEuuGzIm9wj/+8Imgk1Svfrw=="
},
"node_modules/@dzeio/url-manager": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@dzeio/url-manager/-/url-manager-1.0.9.tgz",
- "integrity": "sha512-aBERzF4xznhQhHlzv35fp2cl99N+VjM37CL+R5DJiyIJmgmiz56zru1SKq8Xnv2rulF+6QrrnlT6ZulQDdp9EA==",
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/@dzeio/url-manager/-/url-manager-1.0.10.tgz",
+ "integrity": "sha512-gy8/0yp60crudcqIwsyFsfbHOnG32TAYg14m+at9wzw/bau5URsarlLkO5A3tigk91m05tZQfHLa4xwrXBSY/w==",
"dependencies": {
- "@dzeio/object-util": "^1.4.0"
+ "@dzeio/object-util": "^1.5.0"
}
},
"node_modules/@emmetio/abbreviation": {
diff --git a/package.json b/package.json
index 41a00f8..911ca97 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"@astrojs/tailwind": "^3.1.3",
"@dzeio/logger": "^3.0.0",
"@dzeio/object-util": "^1.5.0",
- "@dzeio/url-manager": "^1.0.9",
+ "@dzeio/url-manager": "^1.0.10",
"astro": "^2.6.4",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.0",
diff --git a/src/env.d.ts b/src/env.d.ts
index 486617c..f03fc50 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -1,6 +1,5 @@
///
///
-///
interface ImportMetaEnv {
PRUSASLICER_PATH?: string
@@ -21,6 +20,14 @@ declare namespace App {
* authentification key is the api key or the session token
*/
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): this
+ removeHeader(key: string): this
+ status(status: number): this
+ build(): Response
+ } // ./libs/ResponseBuilder object
}
}
diff --git a/src/libs/RateLimiter.ts b/src/libs/RateLimiter.ts
index 3053d2f..3522e11 100644
--- a/src/libs/RateLimiter.ts
+++ b/src/libs/RateLimiter.ts
@@ -24,7 +24,7 @@ export default class RateLimiter {
/**
* timeSpan in seconds
*/
- public static timeSpan = 600
+ public static timeSpan = 60
private static instance: RateLimiter = new RateLimiter()
public static getInstance(): RateLimiter {
diff --git a/src/libs/ResponseBuilder.ts b/src/libs/ResponseBuilder.ts
index 25dfe5e..af4a701 100644
--- a/src/libs/ResponseBuilder.ts
+++ b/src/libs/ResponseBuilder.ts
@@ -6,10 +6,12 @@ import { objectLoop } from '@dzeio/object-util'
export default class ResponseBuilder {
private _body: BodyInit | null | undefined
- public body(body: string | object | null | undefined) {
- if (typeof body === 'object') {
+ public body(body: string | Buffer | object | null | undefined) {
+ if (typeof body === 'object' && !(body instanceof Buffer)) {
this._body = JSON.stringify(body)
this.addHeader('Content-Type', 'application/json')
+ } else if (body instanceof Buffer) {
+ this._body = body.toString()
} else {
this._body = body
}
diff --git a/src/libs/validateAuth.ts b/src/libs/validateAuth.ts
index 6b854a2..12e7795 100644
--- a/src/libs/validateAuth.ts
+++ b/src/libs/validateAuth.ts
@@ -2,7 +2,7 @@ import DaoFactory from '../models/DaoFactory'
import CookieManager from './CookieManager'
import { buildRFC7807 } from './RFCs/RFC7807'
-interface Permission {
+export interface Permission {
name: string
/**
* if set it will be usable by users
diff --git a/src/middleware/apiAuth.ts b/src/middleware/apiAuth.ts
index 0d60795..5581c1c 100644
--- a/src/middleware/apiAuth.ts
+++ b/src/middleware/apiAuth.ts
@@ -1,16 +1,43 @@
+import { objectLoop } from '@dzeio/object-util'
+import URLManager from '@dzeio/url-manager'
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 = {
+ '/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
export default defineMiddleware(async (context, next) => {
if (!context.request.url.includes('api')) {
return next()
}
- const auth = await validateAuth(context.request, {
- name: 'slicing.slice',
- api: true,
- cookie: true
- })
+ const permission = objectFind(endpointsPermissions, (_, key) => new URLManager(key).toString(context.params as any) === context.url.pathname)
+ if (!permission) {
+ return buildRFC7807({
+ type: 'idk'
+ })
+ }
+ const auth = await validateAuth(context.request, permission.value)
if (typeof auth === 'object') {
return auth
}
diff --git a/src/middleware/responseBuilder.ts b/src/middleware/responseBuilder.ts
index 10bc260..51b97c4 100644
--- a/src/middleware/responseBuilder.ts
+++ b/src/middleware/responseBuilder.ts
@@ -1,9 +1,21 @@
import { defineMiddleware } from "astro/middleware"
+import { buildRFC7807 } from '../libs/RFCs/RFC7807'
import ResponseBuilder from '../libs/ResponseBuilder'
// `context` and `next` are automatically typed
-export default defineMiddleware(async (context, next) => {
- context.locals.responseBuilder = new ResponseBuilder()
+export default defineMiddleware(async ({ request, locals }, next) => {
+ 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
+ })
+ }
})
diff --git a/src/pages/api/users/[userId]/keys/index.ts b/src/pages/api/users/[userId]/keys/index.ts
deleted file mode 100644
index ff6344c..0000000
--- a/src/pages/api/users/[userId]/keys/index.ts
+++ /dev/null
@@ -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)
- }
-}
diff --git a/src/pages/api/users/index.ts b/src/pages/api/users/index.ts
deleted file mode 100644
index 6c20cad..0000000
--- a/src/pages/api/users/index.ts
+++ /dev/null
@@ -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})
- }
-}
diff --git a/src/pages/api/users/[userId]/configs/[configId]/files/[fileName].ts b/src/pages/api/v1/users/[userId]/configs/[configId]/files/[fileName].ts
similarity index 50%
rename from src/pages/api/users/[userId]/configs/[configId]/files/[fileName].ts
rename to src/pages/api/v1/users/[userId]/configs/[configId]/files/[fileName].ts
index 14639c4..b4472af 100644
--- a/src/pages/api/users/[userId]/configs/[configId]/files/[fileName].ts
+++ b/src/pages/api/v1/users/[userId]/configs/[configId]/files/[fileName].ts
@@ -1,10 +1,8 @@
-import { objectOmit } from '@dzeio/object-util'
import type { APIRoute } from 'astro'
-import { buildRFC7807 } from '../../../../../../../libs/RFCs/RFC7807'
-import DaoFactory from '../../../../../../../models/DaoFactory'
+import { buildRFC7807 } from '../../../../../../../../libs/RFCs/RFC7807'
+import DaoFactory from '../../../../../../../../models/DaoFactory'
-export const get: APIRoute = async ({ params, request }) => {
- const userId = params.userId as string
+export const get: APIRoute = async ({ params, locals }) => {
const configId = params.configId 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)
- return {
- status: 200,
- body: file?.data
- }
+ return locals.responseBuilder
+ .status(200)
+ .body(file?.data)
+ .build()
}
diff --git a/src/pages/api/users/[userId]/configs/index.ts b/src/pages/api/v1/users/[userId]/configs/index.ts
similarity index 63%
rename from src/pages/api/users/[userId]/configs/index.ts
rename to src/pages/api/v1/users/[userId]/configs/index.ts
index 9c3ea5c..76a0f40 100644
--- a/src/pages/api/users/[userId]/configs/index.ts
+++ b/src/pages/api/v1/users/[userId]/configs/index.ts
@@ -1,9 +1,10 @@
import { objectOmit } from '@dzeio/object-util'
import type { APIRoute } from 'astro'
-import { buildRFC7807 } from '../../../../../libs/RFCs/RFC7807'
-import DaoFactory from '../../../../../models/DaoFactory'
+import { buildRFC7807 } from '../../../../../../libs/RFCs/RFC7807'
+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 body = request.body
@@ -37,8 +38,8 @@ export const post: APIRoute = async ({ params, request }) => {
data: buffer
}]
})
- return {
- status: 201,
- body: JSON.stringify(objectOmit(dao ?? {}, 'files'))
- }
+ return locals.responseBuilder
+ .status(StatusCode.CREATED)
+ .body(objectOmit(dao ?? {}, 'files'))
+ .build()
}
diff --git a/src/pages/api/v1/users/[userId]/keys/index.ts b/src/pages/api/v1/users/[userId]/keys/index.ts
new file mode 100644
index 0000000..acabab2
--- /dev/null
+++ b/src/pages/api/v1/users/[userId]/keys/index.ts
@@ -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()
+}
diff --git a/src/pages/api/v1/users/index.ts b/src/pages/api/v1/users/index.ts
new file mode 100644
index 0000000..543fd6f
--- /dev/null
+++ b/src/pages/api/v1/users/index.ts
@@ -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"'
+ })
+}