feat: Add Dao
This commit is contained in:
parent
59db3976fe
commit
5c1ed32cb6
44
.gitignore
vendored
44
.gitignore
vendored
@ -1,23 +1,23 @@
|
||||
# build output
|
||||
dist/
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# build output
|
||||
dist/
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
slicers/*
|
8
.vscode/extensions.json
vendored
8
.vscode/extensions.json
vendored
@ -1,4 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
|
22
.vscode/launch.json
vendored
22
.vscode/launch.json
vendored
@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
164
Dockerfile
164
Dockerfile
@ -1,82 +1,82 @@
|
||||
# This Dockerfile allows you to run NextJS in server mode
|
||||
|
||||
#########
|
||||
# Build #
|
||||
#########
|
||||
FROM node:alpine as BUILD_IMAGE
|
||||
|
||||
# External deps (for node-gyp add: "python3 make g++")
|
||||
# git is used to install the npm packages with git deps
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# run as non root user
|
||||
USER node
|
||||
|
||||
# go to user repository
|
||||
WORKDIR /home/node
|
||||
|
||||
# Add package json
|
||||
ADD --chown=node:node package.json package-lock.json ./
|
||||
|
||||
# install dependencies from package lock
|
||||
RUN npm ci
|
||||
|
||||
# Add project files
|
||||
ADD --chown=node:node . .
|
||||
|
||||
# build
|
||||
RUN npm run build
|
||||
|
||||
RUN npm prune --omit=dev
|
||||
|
||||
##############
|
||||
# Production #
|
||||
##############
|
||||
FROM node:latest 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 \
|
||||
# && apt-get install -y --no-install-recommends \
|
||||
# slic3r \
|
||||
# && rm -rf /var/lib/apt/lists/*
|
||||
# ENV SLICER_PATH slic3r
|
||||
|
||||
# Download & install PrusaSlicer
|
||||
ADD https://github.com/prusa3d/PrusaSlicer/releases/download/version_2.5.2/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 ./
|
||||
RUN tar -xvf PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 -C /opt
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
prusa-slicer \
|
||||
&& apt-get remove prusa-slicer -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV PATH /opt/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201/bin:$PATH
|
||||
ENV SLICER_PATH prusa-slicer
|
||||
|
||||
# run as non root user
|
||||
USER node
|
||||
|
||||
# go to work folder
|
||||
WORKDIR /home/node
|
||||
|
||||
# download & Install Bambu Studio
|
||||
# ADD --chown=node:node https://github.com/bambulab/BambuStudio/releases/download/v01.05.00.61/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage ./
|
||||
# ENV SLICER_PATH /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
# RUN chmod +x /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
|
||||
# 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* ./
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# run it !
|
||||
CMD ["npm", "run", "start"]
|
||||
# This Dockerfile allows you to run NextJS in server mode
|
||||
|
||||
#########
|
||||
# Build #
|
||||
#########
|
||||
FROM node:alpine as BUILD_IMAGE
|
||||
|
||||
# External deps (for node-gyp add: "python3 make g++")
|
||||
# git is used to install the npm packages with git deps
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# run as non root user
|
||||
USER node
|
||||
|
||||
# go to user repository
|
||||
WORKDIR /home/node
|
||||
|
||||
# Add package json
|
||||
ADD --chown=node:node package.json package-lock.json ./
|
||||
|
||||
# install dependencies from package lock
|
||||
RUN npm ci
|
||||
|
||||
# Add project files
|
||||
ADD --chown=node:node . .
|
||||
|
||||
# build
|
||||
RUN npm run build
|
||||
|
||||
RUN npm prune --omit=dev
|
||||
|
||||
##############
|
||||
# Production #
|
||||
##############
|
||||
FROM node:latest 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 \
|
||||
# && apt-get install -y --no-install-recommends \
|
||||
# slic3r \
|
||||
# && rm -rf /var/lib/apt/lists/*
|
||||
# ENV SLICER_PATH slic3r
|
||||
|
||||
# Download & install PrusaSlicer
|
||||
ADD https://github.com/prusa3d/PrusaSlicer/releases/download/version_2.5.2/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 ./
|
||||
RUN tar -xvf PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201.tar.bz2 -C /opt
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
prusa-slicer \
|
||||
&& apt-get remove prusa-slicer -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV PATH /opt/PrusaSlicer-2.5.2+linux-x64-GTK3-202303231201/bin:$PATH
|
||||
ENV SLICER_PATH prusa-slicer
|
||||
|
||||
# run as non root user
|
||||
USER node
|
||||
|
||||
# go to work folder
|
||||
WORKDIR /home/node
|
||||
|
||||
# download & Install Bambu Studio
|
||||
# ADD --chown=node:node https://github.com/bambulab/BambuStudio/releases/download/v01.05.00.61/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage ./
|
||||
# ENV SLICER_PATH /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
# RUN chmod +x /home/node/Bambu_Studio_linux_fedora_v01.05.00.61_20230314200047.AppImage
|
||||
|
||||
# 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* ./
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# run it !
|
||||
CMD ["npm", "run", "start"]
|
||||
|
110
README.md
110
README.md
@ -1,55 +1,55 @@
|
||||
# Astro Starter Kit: Basics
|
||||
|
||||
```
|
||||
npm create astro@latest -- --template basics
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||

|
||||
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
/
|
||||
├── public/
|
||||
│ └── favicon.svg
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ └── Card.astro
|
||||
│ ├── layouts/
|
||||
│ │ └── Layout.astro
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
# Astro Starter Kit: Basics
|
||||
|
||||
```
|
||||
npm create astro@latest -- --template basics
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||

|
||||
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
/
|
||||
├── public/
|
||||
│ └── favicon.svg
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ └── Card.astro
|
||||
│ ├── layouts/
|
||||
│ │ └── Layout.astro
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import node from "@astrojs/node";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://print.dzeio.com',
|
||||
integrations: [tailwind()],
|
||||
build: {
|
||||
assets: 'assets'
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
host: true
|
||||
},
|
||||
trailingSlash: 'never',
|
||||
output: 'server',
|
||||
adapter: node({
|
||||
mode: "standalone"
|
||||
})
|
||||
})
|
||||
import { defineConfig } from 'astro/config';
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import node from "@astrojs/node";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://print.dzeio.com',
|
||||
integrations: [tailwind()],
|
||||
build: {
|
||||
assets: 'assets'
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
host: true
|
||||
},
|
||||
trailingSlash: 'never',
|
||||
output: 'server',
|
||||
adapter: node({
|
||||
mode: "standalone"
|
||||
})
|
||||
})
|
||||
|
12324
package-lock.json
generated
12324
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "paas",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "node ./dist/server/entry.mjs",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^5.2.0",
|
||||
"@astrojs/tailwind": "^3.1.3",
|
||||
"@dzeio/object-util": "^1.5.0",
|
||||
"@dzeio/url-manager": "^1.0.9",
|
||||
"astro": "^2.6.4",
|
||||
"mathjs": "^11.8.1",
|
||||
"tailwindcss": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.3.1"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "paas",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "node ./dist/server/entry.mjs",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^5.2.0",
|
||||
"@astrojs/tailwind": "^3.1.3",
|
||||
"@dzeio/object-util": "^1.5.0",
|
||||
"@dzeio/url-manager": "^1.0.9",
|
||||
"astro": "^2.6.4",
|
||||
"mathjs": "^11.8.1",
|
||||
"mongoose": "^7.3.0",
|
||||
"tailwindcss": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.3.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 758 B |
@ -1,63 +1,63 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body } = Astro.props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h2>
|
||||
{title}
|
||||
<span>→</span>
|
||||
</h2>
|
||||
<p>
|
||||
{body}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.25rem;
|
||||
background-color: white;
|
||||
background-image: none;
|
||||
background-size: 400%;
|
||||
border-radius: 0.6rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1rem 1.3rem;
|
||||
border-radius: 0.35rem;
|
||||
color: #111;
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
p {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
color: #444;
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
background-image: var(--accent-gradient);
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: rgb(var(--accent));
|
||||
}
|
||||
</style>
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body } = Astro.props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h2>
|
||||
{title}
|
||||
<span>→</span>
|
||||
</h2>
|
||||
<p>
|
||||
{body}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.25rem;
|
||||
background-color: white;
|
||||
background-image: none;
|
||||
background-size: 400%;
|
||||
border-radius: 0.6rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1rem 1.3rem;
|
||||
border-radius: 0.35rem;
|
||||
color: #111;
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
p {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
color: #444;
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
background-image: var(--accent-gradient);
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: rgb(var(--accent));
|
||||
}
|
||||
</style>
|
||||
|
21
src/env.d.ts
vendored
21
src/env.d.ts
vendored
@ -1,10 +1,11 @@
|
||||
/// <reference types="astro/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
CONFIGS_PATH?: string
|
||||
PRUSASLICER_PATH?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
/// <reference types="astro/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
CONFIGS_PATH?: string
|
||||
PRUSASLICER_PATH?: string
|
||||
MONGODB?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
@ -1,36 +1,36 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="Astro description">
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
<style is:global>
|
||||
:root {
|
||||
--accent: 124, 58, 237;
|
||||
--accent-gradient: linear-gradient(45deg, rgb(var(--accent)), #da62c4 30%, white 60%);
|
||||
}
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
code {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="Astro description">
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
<style is:global>
|
||||
:root {
|
||||
--accent: 124, 58, 237;
|
||||
--accent-gradient: linear-gradient(45deg, rgb(var(--accent)), #da62c4 30%, white 60%);
|
||||
}
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
code {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { promises as fs } from 'node:fs'
|
||||
|
||||
export default class FilesUtils {
|
||||
public static async exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.stat(path)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
import { promises as fs } from 'node:fs'
|
||||
|
||||
export default class FilesUtils {
|
||||
public static async exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.stat(path)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +1,62 @@
|
||||
|
||||
/**
|
||||
* Add headers:
|
||||
* Content-Type: application/problem+json
|
||||
*
|
||||
* following https://www.rfc-editor.org/rfc/rfc7807.html
|
||||
*/
|
||||
export default interface JSONError {
|
||||
/**
|
||||
* A URI reference [RFC3986] that identifies the
|
||||
* problem type.
|
||||
*
|
||||
* This specification encourages that, when
|
||||
* dereferenced, it provide human-readable documentation for the
|
||||
* problem type (e.g., using HTML [W3C.REC-html5-20141028]).
|
||||
*
|
||||
* When
|
||||
* this member is not present, its value is assumed to be
|
||||
* "about:blank"
|
||||
*/
|
||||
type?: string
|
||||
|
||||
/**
|
||||
* A short, human-readable summary of the problem
|
||||
* type.
|
||||
*
|
||||
* It SHOULD NOT change from occurrence to occurrence of the
|
||||
* problem, except for purposes of localization (e.g., using
|
||||
* proactive content negotiation; see [RFC7231], Section 3.4).
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* The HTTP status code ([RFC7231], Section 6)
|
||||
* generated by the origin server for this occurrence of the problem.
|
||||
*/
|
||||
status?: number
|
||||
|
||||
/**
|
||||
* A human-readable explanation specific to this
|
||||
* occurrence of the problem.
|
||||
*/
|
||||
details?: string
|
||||
|
||||
/**
|
||||
* A URI reference that identifies the specific
|
||||
* occurrence of the problem.
|
||||
*
|
||||
* It may or may not yield further
|
||||
* information if dereferenced.
|
||||
*/
|
||||
instance?: string
|
||||
}
|
||||
|
||||
export function buildRFC7807Error(error: JSONError & Record<string, any>): Response {
|
||||
return new Response(JSON.stringify(error), {
|
||||
headers: {
|
||||
'Content-Type': 'application/problem+json'
|
||||
},
|
||||
status: error.status ?? 500
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add headers:
|
||||
* Content-Type: application/problem+json
|
||||
*
|
||||
* following https://www.rfc-editor.org/rfc/rfc7807.html
|
||||
*/
|
||||
export default interface RFC7807 {
|
||||
/**
|
||||
* A URI reference [RFC3986] that identifies the
|
||||
* problem type.
|
||||
*
|
||||
* This specification encourages that, when
|
||||
* dereferenced, it provide human-readable documentation for the
|
||||
* problem type (e.g., using HTML [W3C.REC-html5-20141028]).
|
||||
*
|
||||
* When
|
||||
* this member is not present, its value is assumed to be
|
||||
* "about:blank"
|
||||
*/
|
||||
type?: string
|
||||
|
||||
/**
|
||||
* A short, human-readable summary of the problem
|
||||
* type.
|
||||
*
|
||||
* It SHOULD NOT change from occurrence to occurrence of the
|
||||
* problem, except for purposes of localization (e.g., using
|
||||
* proactive content negotiation; see [RFC7231], Section 3.4).
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* The HTTP status code ([RFC7231], Section 6)
|
||||
* generated by the origin server for this occurrence of the problem.
|
||||
*/
|
||||
status?: number
|
||||
|
||||
/**
|
||||
* A human-readable explanation specific to this
|
||||
* occurrence of the problem.
|
||||
*/
|
||||
details?: string
|
||||
|
||||
/**
|
||||
* A URI reference that identifies the specific
|
||||
* occurrence of the problem.
|
||||
*
|
||||
* It may or may not yield further
|
||||
* information if dereferenced.
|
||||
*/
|
||||
instance?: string
|
||||
}
|
||||
|
||||
export function buildRFC7807(error: RFC7807 & Record<string, any>): Response {
|
||||
return new Response(JSON.stringify(error), {
|
||||
headers: {
|
||||
'Content-Type': 'application/problem+json'
|
||||
},
|
||||
status: error.status ?? 500
|
||||
})
|
||||
}
|
@ -1,64 +1,64 @@
|
||||
function parseNumber(str: string): number | undefined {
|
||||
if (!/^(\d|\.)+$/g.test(str)) {
|
||||
return undefined
|
||||
}
|
||||
const float = parseFloat(str)
|
||||
const int = parseInt(str, 10)
|
||||
if (isNaN(int)) {
|
||||
return undefined
|
||||
}
|
||||
if (int !== float) {
|
||||
return float
|
||||
}
|
||||
return int
|
||||
}
|
||||
|
||||
function decodeTime(text: string): number {
|
||||
let timeInSec = 0
|
||||
for (const it of text.split(' ')) {
|
||||
const lastChar = it.charAt(it.length - 1)
|
||||
const time = parseInt(it.slice(0, it.length - 1), 10)
|
||||
switch (lastChar) {
|
||||
case 'm':
|
||||
timeInSec += time * 60
|
||||
break;
|
||||
case 's':
|
||||
timeInSec += time
|
||||
break;
|
||||
case 'h':
|
||||
timeInSec += time * 60 * 60
|
||||
break;
|
||||
case 'd':
|
||||
timeInSec += time * 60 * 60 * 24
|
||||
break;
|
||||
default:
|
||||
throw new Error(`error parsing time ${it} (${time})`)
|
||||
}
|
||||
}
|
||||
return timeInSec
|
||||
}
|
||||
|
||||
export function getParams(data: string) {
|
||||
const lines = data.split('\n').filter((it) => it.startsWith(';') && it.includes('='))
|
||||
const obj: Record<string, string | number> = {}
|
||||
for (const line of lines) {
|
||||
const [key, value] = line.split('=', 2).map((it) => it.slice(1).trim())
|
||||
let realKey = key.replace(/ /g, '_').replace(/\[|\]|\(|\)/g, '')
|
||||
const realValue = parseNumber(value) ?? value
|
||||
let offset = 0
|
||||
while (obj[`${realKey}${offset > 0 ? `_${offset}` : ''}`] && obj[`${realKey}${offset > 0 ? `_${offset}` : ''}`] !== realValue) {
|
||||
offset++
|
||||
}
|
||||
if (offset > 0) {
|
||||
realKey = `${realKey}_${offset}`
|
||||
}
|
||||
if (obj[realKey] && obj[realKey] !== realValue) {
|
||||
throw new Error(`Key collision ${key}=${realValue} ${realKey}=${obj[realKey]}`)
|
||||
}
|
||||
obj[realKey] = parseNumber(value) ?? value
|
||||
if (realKey === 'estimated_printing_time_normal_mode') {
|
||||
obj['estimated_printing_time_seconds'] = decodeTime(value)
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
function parseNumber(str: string): number | undefined {
|
||||
if (!/^(\d|\.)+$/g.test(str)) {
|
||||
return undefined
|
||||
}
|
||||
const float = parseFloat(str)
|
||||
const int = parseInt(str, 10)
|
||||
if (isNaN(int)) {
|
||||
return undefined
|
||||
}
|
||||
if (int !== float) {
|
||||
return float
|
||||
}
|
||||
return int
|
||||
}
|
||||
|
||||
function decodeTime(text: string): number {
|
||||
let timeInSec = 0
|
||||
for (const it of text.split(' ')) {
|
||||
const lastChar = it.charAt(it.length - 1)
|
||||
const time = parseInt(it.slice(0, it.length - 1), 10)
|
||||
switch (lastChar) {
|
||||
case 'm':
|
||||
timeInSec += time * 60
|
||||
break;
|
||||
case 's':
|
||||
timeInSec += time
|
||||
break;
|
||||
case 'h':
|
||||
timeInSec += time * 60 * 60
|
||||
break;
|
||||
case 'd':
|
||||
timeInSec += time * 60 * 60 * 24
|
||||
break;
|
||||
default:
|
||||
throw new Error(`error parsing time ${it} (${time})`)
|
||||
}
|
||||
}
|
||||
return timeInSec
|
||||
}
|
||||
|
||||
export function getParams(data: string) {
|
||||
const lines = data.split('\n').filter((it) => it.startsWith(';') && it.includes('='))
|
||||
const obj: Record<string, string | number> = {}
|
||||
for (const line of lines) {
|
||||
const [key, value] = line.split('=', 2).map((it) => it.slice(1).trim())
|
||||
let realKey = key.replace(/ /g, '_').replace(/\[|\]|\(|\)/g, '')
|
||||
const realValue = parseNumber(value) ?? value
|
||||
let offset = 0
|
||||
while (obj[`${realKey}${offset > 0 ? `_${offset}` : ''}`] && obj[`${realKey}${offset > 0 ? `_${offset}` : ''}`] !== realValue) {
|
||||
offset++
|
||||
}
|
||||
if (offset > 0) {
|
||||
realKey = `${realKey}_${offset}`
|
||||
}
|
||||
if (obj[realKey] && obj[realKey] !== realValue) {
|
||||
throw new Error(`Key collision ${key}=${realValue} ${realKey}=${obj[realKey]}`)
|
||||
}
|
||||
obj[realKey] = parseNumber(value) ?? value
|
||||
if (realKey === 'estimated_printing_time_normal_mode') {
|
||||
obj['estimated_printing_time_seconds'] = decodeTime(value)
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
21
src/models/APIKey/ApiKeyDao.ts
Normal file
21
src/models/APIKey/ApiKeyDao.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type APIKey from '.'
|
||||
import Dao from '../Dao'
|
||||
|
||||
export default class APIKeyDao extends Dao<APIKey> {
|
||||
private idx = 0
|
||||
public async create(obj: Omit<APIKey, 'id'>): Promise<APIKey | null> {
|
||||
console.log('pouet', this.idx++)
|
||||
return null
|
||||
// throw new Error('Method not implemented.')
|
||||
}
|
||||
public async findAll(query?: Partial<APIKey> | undefined): Promise<APIKey[]> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
public async update(obj: APIKey): Promise<APIKey | null> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
public async delete(obj: APIKey): Promise<boolean> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
}
|
4
src/models/APIKey/index.ts
Normal file
4
src/models/APIKey/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default interface APIKey {
|
||||
id: string
|
||||
user: string
|
||||
}
|
18
src/models/Client.ts
Normal file
18
src/models/Client.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { MongoClient } from 'mongodb'
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
export default class Client {
|
||||
|
||||
private static connectionString = import.meta.env.MONGODB
|
||||
private static client = false
|
||||
public static async get() {
|
||||
if (!this.connectionString) {
|
||||
throw new Error('Can\'t connect to the database, missing the connection string')
|
||||
}
|
||||
if (!this.client) {
|
||||
mongoose.connect(this.connectionString)
|
||||
this.client = true
|
||||
}
|
||||
return this.client
|
||||
}
|
||||
}
|
21
src/models/Config/ConfigDao.ts
Normal file
21
src/models/Config/ConfigDao.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type Config from '.'
|
||||
import Dao from '../Dao'
|
||||
|
||||
export default class ConfigDao extends Dao<Config> {
|
||||
private idx = 0
|
||||
public async create(obj: Omit<Config, 'id'>): Promise<Config | null> {
|
||||
console.log('pouet', this.idx++)
|
||||
return null
|
||||
// throw new Error('Method not implemented.')
|
||||
}
|
||||
public async findAll(query?: Partial<Config> | undefined): Promise<Config[]> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
public async update(obj: Config): Promise<Config | null> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
public async delete(obj: Config): Promise<boolean> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
}
|
6
src/models/Config/index.ts
Normal file
6
src/models/Config/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type User from '../User'
|
||||
|
||||
export default interface Config {
|
||||
id: string
|
||||
user: string
|
||||
}
|
105
src/models/Dao.ts
Normal file
105
src/models/Dao.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import Client from './Client'
|
||||
|
||||
/**
|
||||
* the Dao is the object that connect the Database or source to the application layer
|
||||
*
|
||||
* you MUST call it through the `DaoFactory` file
|
||||
*/
|
||||
export default abstract class Dao<Object extends { id: any } = { id: any }> {
|
||||
|
||||
/**
|
||||
* insert a new object into the source
|
||||
*
|
||||
* @param obj the object to create
|
||||
* @returns the object with it's id filled if create or null otherwise
|
||||
*/
|
||||
abstract create(obj: Omit<Object, 'id'>): Promise<Object | null>
|
||||
|
||||
/**
|
||||
* insert a new object into the source
|
||||
*
|
||||
* @param obj the object to create
|
||||
* @returns the object with it's id filled if create or null otherwise
|
||||
*/
|
||||
public insert: Dao<Object>['create'] = (obj: Parameters<Dao<Object>['create']>[0]) => this.create(obj)
|
||||
|
||||
/**
|
||||
* find the list of objects having elements from the query
|
||||
*
|
||||
* @param query a partial object which filter depending on the elements, if not set it will fetch everything
|
||||
* @returns an array containing the list of elements that match with the query
|
||||
*/
|
||||
abstract findAll(query?: Partial<Object>): Promise<Array<Object>>
|
||||
|
||||
/**
|
||||
* find the list of objects having elements from the query
|
||||
*
|
||||
* @param query a partial object which filter depending on the elements, if not set it will fetch everything
|
||||
* @returns an array containing the list of elements that match with the query
|
||||
*/
|
||||
public find: Dao<Object>['findAll'] = (query: Parameters<Dao<Object>['findAll']>[0]) => this.findAll(query)
|
||||
|
||||
/**
|
||||
* find an object by it's id
|
||||
*
|
||||
* (shortcut to findOne({id: id}))
|
||||
*
|
||||
* @param id the id of the object
|
||||
* @returns
|
||||
*/
|
||||
public findById(id: Object['id']): Promise<Object | null> {
|
||||
return this.findOne({id: id} as Partial<Object>)
|
||||
}
|
||||
|
||||
/**
|
||||
* find an object by it's id
|
||||
*
|
||||
* (shortcut to findOne({id: id}))
|
||||
*
|
||||
* @param id the id of the object
|
||||
* @returns
|
||||
*/
|
||||
public get(id: Object['id']) {
|
||||
return this.findById(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* find the first element that match `query`
|
||||
*
|
||||
* @param query a partial object which filter depending on the elements, if not set it will fetch everything
|
||||
* @returns the first element matching with the query or null otherwise
|
||||
*/
|
||||
public async findOne(query?: Partial<Object>): Promise<Object | null> {
|
||||
return (await this.findAll(query))[0] ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* update the remote reference of the object
|
||||
*
|
||||
* note: it will not try to insert an item (use `upsert` to handle this)
|
||||
*
|
||||
* @param obj the object to update
|
||||
* @returns an object if it was able to update or null otherwise
|
||||
*/
|
||||
abstract update(obj: Object): Promise<Object | null>
|
||||
|
||||
/**
|
||||
* update the remote reference of the object or create it if not found
|
||||
* @param obj the object to update/insert
|
||||
* @returns the object is updated/inserted or null otherwise
|
||||
*/
|
||||
public async upsert(object: Object | Omit<Object, 'id'>): Promise<Object | null> {
|
||||
if ('id' in object) {
|
||||
return this.update(object)
|
||||
}
|
||||
return this.insert(object)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the object
|
||||
* @param obj the object to delete
|
||||
*
|
||||
* @returns if the object was deleted or not (if object is not in db it will return true)
|
||||
*/
|
||||
abstract delete(obj: Object): Promise<boolean>
|
||||
}
|
63
src/models/DaoFactory.ts
Normal file
63
src/models/DaoFactory.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import Client from './Client'
|
||||
import ConfigDao from './Config/ConfigDao'
|
||||
import Dao from './Dao'
|
||||
import UserDao from './User/UserDao'
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* Add to `DaoItem` your model name
|
||||
* Add to the function `initDao` the Dao
|
||||
*/
|
||||
|
||||
/**
|
||||
* the different Daos that can be initialized
|
||||
*
|
||||
* Touch this interface to define which key is linked to which Dao
|
||||
*/
|
||||
interface DaoItem {
|
||||
config: ConfigDao
|
||||
user: UserDao
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to get any DAO
|
||||
*/
|
||||
export default class DaoFactory {
|
||||
/**
|
||||
* reference of the different Daos for a correct singleton implementation
|
||||
*/
|
||||
private static daos: Partial<DaoItem> = {}
|
||||
|
||||
/**
|
||||
* Get a a dao by its key
|
||||
*
|
||||
* it will throw an error if no Dao exists linked to the item key
|
||||
*
|
||||
* @param key the dao key to get
|
||||
* @returns the Dao you want as a singleton
|
||||
*/
|
||||
public static get<Key extends keyof DaoItem>(key: Key): DaoItem[Key] {
|
||||
if (!(key in this.daos)) {
|
||||
const dao = this.initDao(key)
|
||||
if (!dao) {
|
||||
throw new Error(`${key} has no valid Dao`)
|
||||
}
|
||||
this.daos[key] = dao as DaoItem[Key]
|
||||
}
|
||||
return this.daos[key] as DaoItem[Key]
|
||||
}
|
||||
|
||||
/**
|
||||
* init a dao by its key, it does not care if it exists or not
|
||||
*
|
||||
* @param item the element to init
|
||||
* @returns a new initialized dao or undefined if no dao is linked
|
||||
*/
|
||||
private static initDao(item: keyof DaoItem): Dao<any> | undefined {
|
||||
switch (item) {
|
||||
case 'config': return new ConfigDao()
|
||||
case 'user': return new UserDao()
|
||||
default: return undefined
|
||||
}
|
||||
}
|
||||
}
|
26
src/models/README.md
Normal file
26
src/models/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Models
|
||||
|
||||
this folder contains the Application data layer
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Add a {model}/index.ts contianing your `interface`
|
||||
2. Add a {model}/{model}Dao.ts containing your DAO that `extends` from `Dao.ts`
|
||||
3. Add your Dao to the `DaoFactory.ts` file
|
||||
|
||||
|
||||
## **/index.ts
|
||||
|
||||
file containing the definition of the model
|
||||
|
||||
## **/\*Dao.ts
|
||||
|
||||
File containing the implementation of the Dao
|
||||
|
||||
## Dao.ts
|
||||
|
||||
the Dao.ts is the file each `*Dao.ts` extends from allowing to have a simple, quick and easy to comprehend connectivity
|
||||
|
||||
## DaoFactory.ts
|
||||
|
||||
The DaoFactory file is the file in which you will have the only direct reference to each `*Dao` files and will be sent from there to the rest of the applicaiton layer
|
68
src/models/User/UserDao.ts
Normal file
68
src/models/User/UserDao.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { objectOmit } from '@dzeio/object-util'
|
||||
import mongoose, { ObjectId } from 'mongoose'
|
||||
import User from '.'
|
||||
import Client from '../Client'
|
||||
import Dao from '../Dao'
|
||||
|
||||
export default class UserDao extends Dao<User> {
|
||||
|
||||
private model = mongoose.model('User', new mongoose.Schema({
|
||||
email: { type: String, required: true }
|
||||
}, {
|
||||
timestamps: true
|
||||
}))
|
||||
|
||||
private collection = 'users'
|
||||
|
||||
private idx = 0
|
||||
public async create(obj: Omit<User, 'id' | 'created' | 'updated'>): Promise<User | null> {
|
||||
await Client.get()
|
||||
return this.fromSource(await this.model.create(obj))
|
||||
}
|
||||
public async findAll(query?: Partial<User> | undefined): Promise<User[]> {
|
||||
await Client.get()
|
||||
if (query?.id) {
|
||||
const item = await this.model.findById(new mongoose.Types.ObjectId(query.id))
|
||||
if (!item) {
|
||||
return []
|
||||
}
|
||||
return [this.fromSource(item)]
|
||||
}
|
||||
const resp = await this.model.find(query ? this.toSource(query as User) : {})
|
||||
return resp.map(this.fromSource)
|
||||
|
||||
}
|
||||
public async update(obj: User): Promise<User | null> {
|
||||
await Client.get()
|
||||
|
||||
const query = await this.model.updateOne({
|
||||
_id: new mongoose.Types.ObjectId(obj.id)
|
||||
}, this.toSource(obj))
|
||||
if (query.matchedCount >= 1) {
|
||||
obj.updated = new Date()
|
||||
return obj
|
||||
}
|
||||
return null
|
||||
// return this.fromSource()
|
||||
}
|
||||
public async delete(obj: User): Promise<boolean> {
|
||||
await Client.get()
|
||||
const res = await this.model.deleteOne({
|
||||
_id: new mongoose.Types.ObjectId(obj.id)
|
||||
})
|
||||
return res.deletedCount > 0
|
||||
}
|
||||
|
||||
private toSource(obj: User): Omit<User, 'id'> {
|
||||
return objectOmit(obj, 'id', 'updated', 'created')
|
||||
}
|
||||
|
||||
private fromSource(doc: mongoose.Document<any, any, User>): User {
|
||||
return {
|
||||
id: doc._id.toString(),
|
||||
email: doc.get('email'),
|
||||
updated: doc.get('updatedAt'),
|
||||
created: doc.get('createdAt')
|
||||
}
|
||||
}
|
||||
}
|
6
src/models/User/index.ts
Normal file
6
src/models/User/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default interface User {
|
||||
id: string
|
||||
email: string
|
||||
created: Date
|
||||
updated: Date
|
||||
}
|
@ -1,178 +1,178 @@
|
||||
import { objectMap, objectOmit } from '@dzeio/object-util'
|
||||
import URLManager from '@dzeio/url-manager'
|
||||
import type { APIRoute } from 'astro'
|
||||
import { evaluate } from 'mathjs'
|
||||
import { exec as execSync } from 'node:child_process'
|
||||
import fs from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path/posix'
|
||||
import { promisify } from 'node:util'
|
||||
import FilesUtils from '../../libs/FilesUtils'
|
||||
import { buildRFC7807Error } from '../../libs/HTTPError'
|
||||
import { getParams } from '../../libs/gcodeUtils'
|
||||
|
||||
const exec = promisify(execSync)
|
||||
|
||||
let tmpDir: string
|
||||
|
||||
/**
|
||||
* body is the stl
|
||||
* query
|
||||
* price: algorithm from settings
|
||||
* adionnal settings from https://manual.slic3r.org/advanced/command-line
|
||||
*/
|
||||
export const post: APIRoute = async ({ request }) => {
|
||||
if (!tmpDir) {
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'paas-'))
|
||||
}
|
||||
|
||||
const query = new URLManager(request.url).query()
|
||||
const file = (Math.random() * 1000000).toFixed(0)
|
||||
console.log('started processing new request', file)
|
||||
await fs.mkdir(`${tmpDir}/files`, { recursive: true })
|
||||
|
||||
const overrides = objectOmit(query, 'algo', 'config')
|
||||
const configName = query?.config ?? 'config'
|
||||
let config = `${import.meta.env.CONFIGS_PATH}/` + configName + '.ini'
|
||||
if (!await FilesUtils.exists(config)) {
|
||||
console.log('request finished in error :(', file)
|
||||
return buildRFC7807Error({
|
||||
type: '/missing-config-file',
|
||||
status: 404,
|
||||
title: 'Configuration file is missing',
|
||||
details: `the configuration file "${configName}" is not available on the remote server`
|
||||
})
|
||||
}
|
||||
const stlPath = `${tmpDir}/files/${file}.stl`
|
||||
const gcodePath = `${tmpDir}/files/${file}.gcode`
|
||||
|
||||
// write file
|
||||
await fs.writeFile(stlPath, new Uint8Array(Buffer.from(await request.arrayBuffer())), {
|
||||
encoding: null
|
||||
})
|
||||
// console.log(fs.statSync(stlPath).size, req.body.length)
|
||||
let additionnalParams = objectMap(overrides, (value, key) => `--${(key as string).replace(/_/g, '-')} ${value}`).join(' ')
|
||||
const slicer = import.meta.env.PRUSASLICER_PATH ?? 'prusa-slicer'
|
||||
if (slicer.includes('prusa')) {
|
||||
additionnalParams += ' --export-gcode'
|
||||
}
|
||||
const cmd = `${slicer} ${stlPath} --load ${config} --output ${gcodePath} ${additionnalParams}`
|
||||
try {
|
||||
await exec(cmd, {
|
||||
timeout: 60000
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.log('request finished in error :(', file)
|
||||
const line = e.toString()
|
||||
console.error(e)
|
||||
if (line.includes('Objects could not fit on the bed')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807Error({
|
||||
type: '/object-too-large',
|
||||
status: 413,
|
||||
title: 'Object is too large'
|
||||
})
|
||||
} else if (line.includes('No such file')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807Error({
|
||||
type: '/missing-config-file',
|
||||
status: 404,
|
||||
title: 'Configuration file is missing',
|
||||
details: `the configuration file "${configName}" is not available on the remote server`
|
||||
})
|
||||
} else if (line.includes('Unknown option')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807Error({
|
||||
type: '/slicer-option-unknown',
|
||||
status: 400,
|
||||
title: ' config override doew not exists',
|
||||
details: 'an override does not exists, please contact an administrator or refer to the documentation'
|
||||
})
|
||||
} else if (
|
||||
line.includes('is not recognized as an internal or external command') ||
|
||||
line.includes('.dll was not loaded')
|
||||
) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807Error({
|
||||
type: '/slicer-not-found',
|
||||
status: 408,
|
||||
title: 'the slicer used to process this file has not been found',
|
||||
details: 'the server has a misconfiguration meaning that we can\'t process the request, please contact an administrator',
|
||||
additionnalInfo: line.includes('dll') ? 'Missing DLL' : 'Slicer not found '
|
||||
})
|
||||
} else if (line.includes('ETIMEDOUT')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807Error({
|
||||
type: '/timed-out-slicing',
|
||||
status: 408,
|
||||
title: 'Timed out slicing file',
|
||||
detail: `The file you are trying to process takes too long to be processed`,
|
||||
processingTimeoutMillis: 60000
|
||||
})
|
||||
}
|
||||
return buildRFC7807Error({
|
||||
type: '/general-input-output-error',
|
||||
status: 500,
|
||||
title: 'General I/O error',
|
||||
details: 'A server error make it impossible to slice the file, please contact an administrator with the json error',
|
||||
fileId: file,
|
||||
config: configName,
|
||||
// fileSize: req.body.length,
|
||||
overrides: overrides,
|
||||
serverMessage:
|
||||
e.toString().replace(cmd, '***SLICER***').replace(stlPath, configName ?? `***FILE***`).replace(`${path}/configs/`, '').replace('.ini', '')
|
||||
})
|
||||
}
|
||||
const gcode = await fs.readFile(gcodePath, 'utf-8')
|
||||
await fs.rm(stlPath)
|
||||
await fs.rm(gcodePath)
|
||||
const params = getParams(gcode)
|
||||
let price: string | undefined
|
||||
if (query?.algo) {
|
||||
let algo = decodeURI(query.algo as string)
|
||||
// objectLoop(params, (value, key) => {
|
||||
// if (typeof value !== 'number') {
|
||||
// return
|
||||
// }
|
||||
// while (algo.includes(key)) {
|
||||
// algo = algo.replace(key, value.toString())
|
||||
// }
|
||||
// })
|
||||
try {
|
||||
const tmp = evaluate(algo, params)
|
||||
if (typeof tmp === 'number') {
|
||||
price = tmp.toFixed(2)
|
||||
} else {
|
||||
return buildRFC7807Error({
|
||||
type: '/algorithm-error',
|
||||
status: 500,
|
||||
title: 'Algorithm compilation error',
|
||||
details: 'It seems the algorithm resolution failed',
|
||||
algorithm: algo,
|
||||
algorithmError: 'Algorithm return a Unit',
|
||||
parameters: params
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.dir(e)
|
||||
return buildRFC7807Error({
|
||||
type: '/algorithm-error',
|
||||
status: 500,
|
||||
title: 'Algorithm compilation error',
|
||||
details: 'It seems the algorithm resolution failed',
|
||||
algorithm: algo,
|
||||
algorithmError: e,
|
||||
parameters: params
|
||||
})
|
||||
}
|
||||
}
|
||||
console.log('request successfull :)', file)
|
||||
return {
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
price: price ? parseFloat(price) : undefined,
|
||||
...getParams(gcode),
|
||||
gcode
|
||||
})
|
||||
}
|
||||
}
|
||||
import { objectMap, objectOmit } from '@dzeio/object-util'
|
||||
import URLManager from '@dzeio/url-manager'
|
||||
import type { APIRoute } from 'astro'
|
||||
import { evaluate } from 'mathjs'
|
||||
import { exec as execSync } from 'node:child_process'
|
||||
import fs from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path/posix'
|
||||
import { promisify } from 'node:util'
|
||||
import FilesUtils from '../../../libs/FilesUtils'
|
||||
import { buildRFC7807 } from '../../../libs/RFCs/RFC7807'
|
||||
import { getParams } from '../../../libs/gcodeUtils'
|
||||
|
||||
const exec = promisify(execSync)
|
||||
|
||||
let tmpDir: string
|
||||
|
||||
/**
|
||||
* body is the stl
|
||||
* query
|
||||
* price: algorithm from settings
|
||||
* adionnal settings from https://manual.slic3r.org/advanced/command-line
|
||||
*/
|
||||
export const post: APIRoute = async ({ request }) => {
|
||||
if (!tmpDir) {
|
||||
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'paas-'))
|
||||
}
|
||||
|
||||
const query = new URLManager(request.url).query()
|
||||
const file = (Math.random() * 1000000).toFixed(0)
|
||||
console.log('started processing new request', file)
|
||||
await fs.mkdir(`${tmpDir}/files`, { recursive: true })
|
||||
|
||||
const overrides = objectOmit(query, 'algo', 'config')
|
||||
const configName = query?.config ?? 'config'
|
||||
let config = `${import.meta.env.CONFIGS_PATH}/` + configName + '.ini'
|
||||
if (!await FilesUtils.exists(config)) {
|
||||
console.log('request finished in error :(', file)
|
||||
return buildRFC7807({
|
||||
type: '/missing-config-file',
|
||||
status: 404,
|
||||
title: 'Configuration file is missing',
|
||||
details: `the configuration file "${configName}" is not available on the remote server`
|
||||
})
|
||||
}
|
||||
const stlPath = `${tmpDir}/files/${file}.stl`
|
||||
const gcodePath = `${tmpDir}/files/${file}.gcode`
|
||||
|
||||
// write file
|
||||
await fs.writeFile(stlPath, new Uint8Array(Buffer.from(await request.arrayBuffer())), {
|
||||
encoding: null
|
||||
})
|
||||
// console.log(fs.statSync(stlPath).size, req.body.length)
|
||||
let additionnalParams = objectMap(overrides, (value, key) => `--${(key as string).replace(/_/g, '-')} ${value}`).join(' ')
|
||||
const slicer = import.meta.env.PRUSASLICER_PATH ?? 'prusa-slicer'
|
||||
if (slicer.includes('prusa')) {
|
||||
additionnalParams += ' --export-gcode'
|
||||
}
|
||||
const cmd = `${slicer} ${stlPath} --load ${config} --output ${gcodePath} ${additionnalParams}`
|
||||
try {
|
||||
await exec(cmd, {
|
||||
timeout: 60000
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.log('request finished in error :(', file)
|
||||
const line = e.toString()
|
||||
console.error(e)
|
||||
if (line.includes('Objects could not fit on the bed')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807({
|
||||
type: '/object-too-large',
|
||||
status: 413,
|
||||
title: 'Object is too large'
|
||||
})
|
||||
} else if (line.includes('No such file')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807({
|
||||
type: '/missing-config-file',
|
||||
status: 404,
|
||||
title: 'Configuration file is missing',
|
||||
details: `the configuration file "${configName}" is not available on the remote server`
|
||||
})
|
||||
} else if (line.includes('Unknown option')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807({
|
||||
type: '/slicer-option-unknown',
|
||||
status: 400,
|
||||
title: ' config override doew not exists',
|
||||
details: 'an override does not exists, please contact an administrator or refer to the documentation'
|
||||
})
|
||||
} else if (
|
||||
line.includes('is not recognized as an internal or external command') ||
|
||||
line.includes('.dll was not loaded')
|
||||
) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807({
|
||||
type: '/slicer-not-found',
|
||||
status: 408,
|
||||
title: 'the slicer used to process this file has not been found',
|
||||
details: 'the server has a misconfiguration meaning that we can\'t process the request, please contact an administrator',
|
||||
additionnalInfo: line.includes('dll') ? 'Missing DLL' : 'Slicer not found '
|
||||
})
|
||||
} else if (line.includes('ETIMEDOUT')) {
|
||||
await fs.rm(stlPath)
|
||||
return buildRFC7807({
|
||||
type: '/timed-out-slicing',
|
||||
status: 408,
|
||||
title: 'Timed out slicing file',
|
||||
detail: `The file you are trying to process takes too long to be processed`,
|
||||
processingTimeoutMillis: 60000
|
||||
})
|
||||
}
|
||||
return buildRFC7807({
|
||||
type: '/general-input-output-error',
|
||||
status: 500,
|
||||
title: 'General I/O error',
|
||||
details: 'A server error make it impossible to slice the file, please contact an administrator with the json error',
|
||||
fileId: file,
|
||||
config: configName,
|
||||
// fileSize: req.body.length,
|
||||
overrides: overrides,
|
||||
serverMessage:
|
||||
e.toString().replace(cmd, '***SLICER***').replace(stlPath, configName ?? `***FILE***`).replace(`${path}/configs/`, '').replace('.ini', '')
|
||||
})
|
||||
}
|
||||
const gcode = await fs.readFile(gcodePath, 'utf-8')
|
||||
await fs.rm(stlPath)
|
||||
await fs.rm(gcodePath)
|
||||
const params = getParams(gcode)
|
||||
let price: string | undefined
|
||||
if (query?.algo) {
|
||||
let algo = decodeURI(query.algo as string)
|
||||
// objectLoop(params, (value, key) => {
|
||||
// if (typeof value !== 'number') {
|
||||
// return
|
||||
// }
|
||||
// while (algo.includes(key)) {
|
||||
// algo = algo.replace(key, value.toString())
|
||||
// }
|
||||
// })
|
||||
try {
|
||||
const tmp = evaluate(algo, params)
|
||||
if (typeof tmp === 'number') {
|
||||
price = tmp.toFixed(2)
|
||||
} else {
|
||||
return buildRFC7807({
|
||||
type: '/algorithm-error',
|
||||
status: 500,
|
||||
title: 'Algorithm compilation error',
|
||||
details: 'It seems the algorithm resolution failed',
|
||||
algorithm: algo,
|
||||
algorithmError: 'Algorithm return a Unit',
|
||||
parameters: params
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.dir(e)
|
||||
return buildRFC7807({
|
||||
type: '/algorithm-error',
|
||||
status: 500,
|
||||
title: 'Algorithm compilation error',
|
||||
details: 'It seems the algorithm resolution failed',
|
||||
algorithm: algo,
|
||||
algorithmError: e,
|
||||
parameters: params
|
||||
})
|
||||
}
|
||||
}
|
||||
console.log('request successfull :)', file)
|
||||
return {
|
||||
status: 200,
|
||||
body: JSON.stringify({
|
||||
price: price ? parseFloat(price) : undefined,
|
||||
...getParams(gcode),
|
||||
gcode
|
||||
})
|
||||
}
|
||||
}
|
@ -1,36 +1,36 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Card from '../components/Card.astro';
|
||||
---
|
||||
|
||||
<Layout title="Welcome to Astro.">
|
||||
<main>
|
||||
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
||||
<p class="instructions">
|
||||
To get started, open the directory <code>src/pages</code> in your project.<br />
|
||||
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
|
||||
</p>
|
||||
<ul role="list" class="link-card-grid">
|
||||
<Card
|
||||
href="https://docs.astro.build/"
|
||||
title="Documentation"
|
||||
body="Learn how Astro works and explore the official API docs."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/integrations/"
|
||||
title="Integrations"
|
||||
body="Supercharge your project with new frameworks and libraries."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/themes/"
|
||||
title="Themes"
|
||||
body="Explore a galaxy of community-built starter themes."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/chat/"
|
||||
title="Community"
|
||||
body="Come say hi to our amazing Discord community. ❤️"
|
||||
/>
|
||||
</ul>
|
||||
</main>
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Card from '../components/Card.astro';
|
||||
---
|
||||
|
||||
<Layout title="Welcome to Astro.">
|
||||
<main>
|
||||
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
||||
<p class="instructions">
|
||||
To get started, open the directory <code>src/pages</code> in your project.<br />
|
||||
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
|
||||
</p>
|
||||
<ul role="list" class="link-card-grid">
|
||||
<Card
|
||||
href="https://docs.astro.build/"
|
||||
title="Documentation"
|
||||
body="Learn how Astro works and explore the official API docs."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/integrations/"
|
||||
title="Integrations"
|
||||
body="Supercharge your project with new frameworks and libraries."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/themes/"
|
||||
title="Themes"
|
||||
body="Explore a galaxy of community-built starter themes."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/chat/"
|
||||
title="Community"
|
||||
body="Come say hi to our amazing Discord community. ❤️"
|
||||
/>
|
||||
</ul>
|
||||
</main>
|
||||
</Layout>
|
@ -1,8 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
41
test.ts
Normal file
41
test.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import DaoFactory from './src/models/DaoFactory'
|
||||
|
||||
(async () => {
|
||||
// await DaoFactory.get('config').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('user').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('config').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('user').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('config').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('config').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('config').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('user').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('config').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('config').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('user').create({user: {id: 'pouet'}})
|
||||
// await DaoFactory.get('user').create({user: {id: 'pouet'}})
|
||||
|
||||
const dao = DaoFactory.get('user')
|
||||
console.log(await dao.create({email: 'pokemon@go.com'}))
|
||||
const obj = await dao.get('648f82be60a03b7398d36925')
|
||||
console.log(obj)
|
||||
if (!obj) {
|
||||
console.log('no obj :(')
|
||||
} else {
|
||||
console.log('object :)', obj)
|
||||
obj.email += 'jesuisundieu@pokemon.com'
|
||||
console.log(await dao.update(obj))
|
||||
}
|
||||
|
||||
const toDelete = await dao.findOne({email: 'pokemon@go.com'})
|
||||
if (toDelete) {
|
||||
console.log('todelete :)', toDelete)
|
||||
await dao.delete(toDelete)
|
||||
}
|
||||
|
||||
console.log()
|
||||
console.log('done')
|
||||
process.exit(0)
|
||||
})()
|
||||
|
||||
// await mongoose.get('id')
|
||||
// await fetch(`/api/users/${'id'}`).then((it) => it.json())
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user