diff --git a/.env b/.env index 54cc99f..511978f 100644 --- a/.env +++ b/.env @@ -11,6 +11,12 @@ SENTRY_PROJECT= RSYNC_HOST=ftp1.webmakers.de RSYNC_PORT=22223 +PRODUCTION_SERVER=dock4.basehosts.de +PRODUCTION_TIBI_PREFIX=wmbasic +PRODUCTION_PATH=/webroots2/customers/_CUSTOMER_ID_/____ + +STAGING_PATH=/staging/__ORG__/__PROJECT__/dev + LIVE_URL=https://www STAGING_URL=https://dev-__PROJECT_NAME__.staging.testversion.online diff --git a/.prettierrc b/.prettierrc index 5aa9151..1923026 100644 --- a/.prettierrc +++ b/.prettierrc @@ -10,7 +10,7 @@ "check-parameters" ], "no-var-keyword": true, - "svelteSortOrder": "scripts-markup-styles", + "svelteSortOrder": "scripts-options-markup-styles", "svelteStrictMode": true, "svelteBracketNewLine": true, "svelteAllowShorthand": true, diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6e3111e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:latest + +ADD webserver/ /webserver/ +WORKDIR /webserver +RUN npm install +EXPOSE 80 + +ADD frontend/ /data/ + +ENV INDEX "" +ENV API "" +ENV WEBROOT "" + +CMD [ "node", "webserver.js" ] diff --git a/Makefile b/Makefile index 864c7dc..f759581 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,16 @@ -DOCKER_COMPOSE := docker compose -f docker-compose-local.yml -.DEFAULT_GOAL := help -.PHONY: docker-up docker-start docker-down docker-logs +DOCKER_COMPOSE=docker compose -f docker-compose-local.yml -export UID := $(shell id -u) -export GID := $(shell id -g) +.DEFAULT_GOAL := help + +.PHONY: docker-up docker-up-tibi-dev docker-start docker-start-tibi-dev docker-down docker-ps docker-logs yarn-upgrade fix-permissions + +include ./.env help: ## show this help @echo MAKE TARGETS - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + + docker-up: ## bring docker compose stack up in background $(DOCKER_COMPOSE) --profile tibi up -d @@ -15,27 +18,58 @@ docker-up: ## bring docker compose stack up in background docker-up-tibi-dev: ## bring docker compose stack up in background with tibi-dev $(DOCKER_COMPOSE) --profile tibi-dev up -d +docker-up-chisel: ## bring up chisel tunnel + $(DOCKER_COMPOSE) --profile chisel up -d + docker-down: ## take docker compose stack down - $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi down + $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi --profile chisel down docker-start: ## start docker compose stack in foreground and take it down after CTRL-C - $(DOCKER_COMPOSE) --profile tibi up; $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi down + $(DOCKER_COMPOSE) --profile tibi up; $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi --profile chisel down docker-start-tibi-dev: ## start docker compose stack in foreground and take it down after CTRL-C (with tibi-dev) - $(DOCKER_COMPOSE) --profile tibi-dev up; $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi down + $(DOCKER_COMPOSE) --profile tibi-dev up; $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi --profile chisel down docker-ps: ## show container state - $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi ps + $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi --profile chisel ps docker-logs: ## show docker logs and follow - $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi logs -f || true + $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi --profile chisel logs -f --tail=100 || true + +docker-pull: ## pull docker images + $(DOCKER_COMPOSE) --profile tibi-dev --profile tibi --profile chisel pull docker-%: $(DOCKER_COMPOSE) $* -yarn-upgrade: # interactive yarn upgrade +yarn-upgrade: ## interactive yarn upgrade $(DOCKER_COMPOSE) run --rm yarnstart yarn upgrade-interactive $(DOCKER_COMPOSE) restart yarnstart -fix-permissions: # set files/directories owner to UID:GID from .env - sudo chown -R $(UID):$(GID) ./ +fix-permissions: ## set files/directories owner to UID:GID from .env + sudo chown -R $(CODER_UID):$(CODER_GID) ./ + +mongo-sync-master-to-local: ## sync mongo from master to local + $(DOCKER_COMPOSE) up mongo -d + read -s -p "Enter chisel password: " CHISEL_PASSWORD; \ + chisel client --auth coder:$$CHISEL_PASSWORD http://$(PRODUCTION_SERVER):10987 27017:mongo:27017 & + sleep 3 + mongodump --archive --gzip --db=$(PRODUCTION_TIBI_PREFIX)_$(TIBI_NAMESPACE) | $(DOCKER_COMPOSE) exec -T mongo mongorestore --archive --gzip --nsFrom='$(PRODUCTION_TIBI_PREFIX)_$(TIBI_NAMESPACE).*' --nsTo='tibi_$(TIBI_NAMESPACE).*' --nsInclude='$(PRODUCTION_TIBI_PREFIX)_$(TIBI_NAMESPACE).*' --drop + sleep 3 + killall chisel + +media-sync-master-to-local: ## sync attachments from master to local + rsync -v -e "ssh -i ~/.ssh/id_deploy -p 22223 -l 10052300,33,$(PRODUCTION_PATH)/media" -az --info=progress2 ftp1.webmakers.de:/ media/ + +mongo-sync-local-to-staging: ## sync mongo from local to staging area + $(DOCKER_COMPOSE) up mongo -d + read -s -p "Enter chisel password: " CHISEL_PASSWORD; \ + chisel client --auth coder:$$CHISEL_PASSWORD https://chisel-dev-tibi.staging.testversion.online 27017:mongo:27017 & + sleep 3 + + $(DOCKER_COMPOSE) exec -T mongo mongodump --archive --gzip --db=$(TIBI_PREFIX)_$(TIBI_NAMESPACE) | mongorestore --archive --gzip --nsFrom='$(TIBI_PREFIX)_$(TIBI_NAMESPACE).*' --nsTo='$(TIBI_PREFIX)_$(TIBI_NAMESPACE).*' --nsInclude='$(TIBI_PREFIX)_$(TIBI_NAMESPACE).*' --drop + sleep 3 + killall chisel + +media-sync-local-to-staging: ## sync attachments from local to staging area + sudo rsync -v -az --info=progress2 media $(STAGING_PATH)/ diff --git a/README.md b/README.md index 1c0f76f..f76ca27 100644 --- a/README.md +++ b/README.md @@ -27,24 +27,6 @@ git lfs pull yarn install ``` -### Entwickeln mit dev-Webserver - -```sh -yarn start -``` - -oder mit abweichender API für "/api"-Proxy - -```sh -API_BASE=https://login.tibicms.de/api/v1_/__NAMESPACE__ yarn start -``` - -### Entwickeln mit externem Webserver (z.B. vscode live server) - -```sh -yarn dev -``` - ### Entwickeln auf dem Code-Server mit Docker Compose Stack ```sh @@ -58,17 +40,9 @@ make docker-down | UI | URL | | --- | --- | -| Website | https://tibi-svelte-starter.code.testversion.online/ | -| Tibi Admin | https://tibi-svelte-starter-tibiadmin.code.testversion.online/ | -| Admin Mongo | https://tibi-svelte-starter-adminmongo.code.testversion.online/ | -| Maildev | https://tibi-svelte-starter-maildev.code.testversion.online/ | - -### Testen - -```sh -yarn build:istanbul # instrumentiert Code für coverage-Report -yarn cy:docker:run # oder mit Xserver und UI cy:docker:open -``` +| Website | | +| Tibi Admin | | +| Maildev | | ### Bauen @@ -81,4 +55,7 @@ yarn build:legacy # serverseitiges Rendering yarn build:server + +# Admin-Module +yarn build:admin ``` diff --git a/babel.config.json b/babel.config.json index 0e70282..bfc729c 100644 --- a/babel.config.json +++ b/babel.config.json @@ -10,16 +10,11 @@ "version": "3", "proposals": true }, - "targets": ">0.5%, IE 11, not dead" + "targets": ">0.5%, IE 11, not dead", + "debug": true, + "forceAllTransforms": true } ] ], - "plugins": [ - [ - "@babel/plugin-transform-spread", - { - "loose": true - } - ] - ] + "plugins": [] } diff --git a/babel.config.server.json b/babel.config.server.json new file mode 100644 index 0000000..4f19fab --- /dev/null +++ b/babel.config.server.json @@ -0,0 +1,5 @@ +{ + "sourceMaps": "inline", + "inputSourceMap": true, + "plugins": [["@babel/plugin-transform-async-to-generator"]] +} diff --git a/docker-compose-local.yml b/docker-compose-local.yml index a0b2361..a218567 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -2,12 +2,18 @@ name: ${PROJECT_NAME} services: yarnstart: - image: node:18 - user: ${UID}:${GID} + profiles: + - tibi + - tibi-dev + image: node:20 volumes: - ./:/data + - ./tmp:/tmp + - ./tmp/nonexistent:/nonexistent + - ./tmp/.npm:/.npm + - ./tmp/.yarn:/.yarn working_dir: /data - command: sh -c "yarn install && API_BASE=http://tibiserver:8080/api/v1/_/${TIBI_NAMESPACE} yarn start" + command: sh -c "yarn install && API_BASE=http://tibiserver:8080/api/v1/_/${TIBI_NAMESPACE} yarn start${START_SCRIPT}" expose: - 3000 labels: @@ -15,25 +21,36 @@ services: - online.testversion.code.subdomain=${PROJECT_NAME} - traefik.http.routers.${PROJECT_NAME}-yarnstart.middlewares=${PROJECT_NAME}-yarnstart - traefik.http.middlewares.${PROJECT_NAME}-yarnstart.basicauth.usersfile=${PWD}/.basic-auth-web + user: ${CODER_UID}:${CODER_GID} tibiserver: - image: gitbase.de/cms/tibi-server profiles: - tibi + image: gitbase.de/cms/tibi-server volumes: - ./:/data environment: DB_DIAL: mongodb://mongo DB_PREFIX: ${TIBI_PREFIX} MAIL_HOST: maildev:25 + SECURITY_ALLOWABSOLUTEPATHS: "true" + SECURITY_ALLOWUPPERPATHS: "true" depends_on: - mongo + expose: + - 8080 + labels: + - traefik.enable=true + - traefik.http.services.${PROJECT_NAME}-tibiserver.loadbalancer.server.port=8080 + - online.testversion.code.subdomain=${PROJECT_NAME}-tibiserver tibiserver-dev: hostname: tibiserver + build: + context: ./ + dockerfile: ./../../cms/tibi-server/Dockerfile.air profiles: - tibi-dev - image: cosmtrek/air volumes: - ./:/data - ./../../cms/tibi-server:/tibi-server @@ -44,14 +61,22 @@ services: DB_DIAL: mongodb://mongo DB_PREFIX: ${TIBI_PREFIX} MAIL_HOST: maildev:25 + SECURITY_ALLOWABSOLUTEPATHS: "true" + SECURITY_ALLOWUPPERPATHS: "true" depends_on: - mongo - user: ${UID}:${GID} + user: ${CODER_UID}:${CODER_GID} + expose: + - 8080 + labels: + - traefik.enable=true + - traefik.http.services.${PROJECT_NAME}-tibiserver.loadbalancer.server.port=8080 + - online.testversion.code.subdomain=${PROJECT_NAME}-tibiserver tibiadmin: - image: gitbase.de/cms/tibi-admin profiles: - tibi + image: gitbase.de/cms/tibi-admin environment: INDEX: spa.html WEBROOT: /data @@ -70,7 +95,7 @@ services: tibiadmin-dev: profiles: - tibi-dev - image: node:18 + image: node:20 volumes: - ./../../cms/tibi-admin:/data working_dir: /data @@ -82,20 +107,24 @@ services: - online.testversion.code.subdomain=${PROJECT_NAME}-tibiadmin-dev - traefik.http.routers.${PROJECT_NAME}-tibiadmin-dev.middlewares=${PROJECT_NAME}-tibiadmin-dev - traefik.http.middlewares.${PROJECT_NAME}-tibiadmin-dev.basicauth.usersfile=${PWD}/.basic-auth-code - user: ${UID}:${GID} + user: ${CODER_UID}:${CODER_GID} mongo: + profiles: + - tibi + - tibi-dev image: gitbase.de/server/mongo:4.2 - user: ${UID}:${GID} volumes: - ./tmp/mongo-data:/data/db + user: ${CODER_UID}:${CODER_GID} adminmongo: + profiles: + - tibi + - tibi-dev image: gitbase.de/server/adminmongo environment: CONN_NAME: mongo - # DB_USERNAME: root - # DB_PASSWORD: root DB_HOST: mongo PORT: 1234 expose: @@ -107,6 +136,9 @@ services: - traefik.http.middlewares.${PROJECT_NAME}-adminmongo.basicauth.usersfile=${PWD}/.basic-auth-code maildev: + profiles: + - tibi + - tibi-dev image: maildev/maildev command: node bin/maildev --web 1080 --smtp 25 -v --hide-extensions=STARTTLS expose: @@ -117,3 +149,14 @@ services: - traefik.http.services.${PROJECT_NAME}-maildev.loadbalancer.server.port=1080 - traefik.http.routers.${PROJECT_NAME}-maildev.middlewares=${PROJECT_NAME}-maildev - traefik.http.middlewares.${PROJECT_NAME}-maildev.basicauth.usersfile=${PWD}/.basic-auth-code + + chisel: + profiles: + - chisel + image: jpillora/chisel + expose: + - 8080 + labels: + - traefik.enable=true + - online.testversion.code.subdomain=${PROJECT_NAME}-chisel + command: server --port 8080 --auth coder:coder diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml new file mode 100644 index 0000000..b6dfe4c --- /dev/null +++ b/docker-compose-staging.yml @@ -0,0 +1,14 @@ +services: + www: + build: . + image: ${PROJECT_NAME}:dev + container_name: dev-${PROJECT_NAME} + environment: + #INDEX: spa.html + SSR: https://dev-tibi-server.staging.testversion.online/api/v1/_/${TIBI_NAMESPACE}/ssr?url= + WEBROOT: /data + API: /tibiapi:https://dev-tibi-server.staging.testversion.online/api/v1/_/${TIBI_NAMESPACE} + PORT: 80 + labels: + traefik.enable: "true" + online.testversion.staging.subdomain: dev-${PROJECT_NAME} diff --git a/esbuild.config.admin.js b/esbuild.config.admin.js new file mode 100644 index 0000000..454cd72 --- /dev/null +++ b/esbuild.config.admin.js @@ -0,0 +1,22 @@ +const config = require("./esbuild.config.js") +const svelteConfig = require("./svelte.config") + +config.options.minify = false +config.options.entryPoints = ["./frontend/src/admin.ts"] +config.options.outfile = "./" + config.distDir + "/admin.mjs" +delete config.options.outdir +config.options.splitting = false +config.options.plugins = [ + config.sveltePlugin({ + compilerOptions: { + css: false, + hydratable: false, + dev: (process.argv?.length > 2 ? process.argv[2] : "build") !== "build", + }, + preprocess: svelteConfig.preprocess, + cache: true, + }), + config.resolvePlugin, +] + +module.exports = config diff --git a/esbuild.config.js b/esbuild.config.js index dce28dd..7c2dfb1 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -1,3 +1,5 @@ +const fs = require("fs") + const resolvePlugin = { name: "resolvePlugin", setup(build) { @@ -15,11 +17,12 @@ const resolvePlugin = { const sveltePlugin = require("esbuild-svelte") -const distDir = "frontend" +const frontendDir = "./frontend" +const distDir = frontendDir + "/dist" -console.log("copy public dir...") -const copydir = require("copy-dir") -copydir.sync(__dirname + "/public", __dirname + "/" + distDir) +// console.log("copy public dir...") +// const copydir = require("copy-dir") +// copydir.sync(__dirname + "/public", __dirname + "/" + distDir) /*copydir.sync( __dirname + "/public/index.html", __dirname + "/" + distDir + "/template.html" @@ -28,22 +31,27 @@ copydir.sync(__dirname + "/public", __dirname + "/" + distDir) const svelteConfig = require("./svelte.config") const esbuildSvelte = sveltePlugin({ compilerOptions: { - css: false, + css: "external", hydratable: true, dev: (process.argv?.length > 2 ? process.argv[2] : "build") !== "build", }, preprocess: svelteConfig.preprocess, cache: true, + filterWarnings: (warning) => { + // filter out a11y + if (warning.code.match(/^a11y/)) return false + return true + }, }) const options = { logLevel: "info", color: true, - entryPoints: ["./src/index.ts"], - outfile: "./" + distDir + "/_dist_/index.mjs", - metafile: true, //"./" + distDir + "/_dist_/meta.json", + entryPoints: ["./frontend/src/index.ts"], + outfile: distDir + "/index.mjs", + metafile: true, format: "esm", - minify: true, + minify: process.argv[2] == "build", bundle: true, splitting: false, plugins: [esbuildSvelte, resolvePlugin], @@ -53,8 +61,6 @@ const options = { ".eot": "file", ".svg": "file", ".ttf": "file", - ".png": "file", - ".jpg": "file", }, sourcemap: true, target: ["es2020", "chrome61", "firefox60", "safari11", "edge18"], @@ -64,22 +70,44 @@ const bsMiddleware = [] if (process.argv[2] == "start") { const { createProxyMiddleware } = require("http-proxy-middleware") - const apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + process.env.NAMESPACE + const dotEnv = fs.readFileSync(__dirname + "/.env", "utf8") + const TIBI_NAMESPACE = dotEnv.match(/TIBI_NAMESPACE=(.*)/)[1] + const apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + TIBI_NAMESPACE bsMiddleware.push( - createProxyMiddleware("/api", { + createProxyMiddleware({ + pathFilter: "/api", target: apiBase, pathRewrite: { "^/api": "" }, changeOrigin: true, + logLevel: "debug", }) ) + + if (process.env.SSR) { + bsMiddleware.push( + createProxyMiddleware({ + pathFilter: function (path, req) { + return !path.match(/\./) + }, + target: apiBase, + changeOrigin: true, + logLevel: "debug", + pathRewrite: function (path, req) { + console.log(path) + return "/ssr?url=" + encodeURIComponent(path) + }, + }) + ) + } } module.exports = { sveltePlugin: sveltePlugin, resolvePlugin: resolvePlugin, options: options, + distDir, watch: { - path: [__dirname + "/src/**/*"], + path: [__dirname + "/" + frontendDir + "/src/**/*"], }, serve: { onRequest(args) { @@ -88,7 +116,7 @@ module.exports = { }, browserSync: { server: { - baseDir: distDir, + baseDir: frontendDir, middleware: [ require("morgan")("dev"), ...bsMiddleware, diff --git a/esbuild.config.server.js b/esbuild.config.server.js index b84f9b6..6269fc7 100644 --- a/esbuild.config.server.js +++ b/esbuild.config.server.js @@ -5,7 +5,10 @@ config.options.sourcemap = "inline" config.options.minify = false config.options.platform = "node" config.options.format = "cjs" -config.options.entryPoints = ["./src/ssr.ts"] +// es2015 will transform async/await to generators, but not working with svelte +// so we need babel to transform async/await to promises +// config.options.target = "es2015" +config.options.entryPoints = ["./frontend/src/ssr.ts"] config.options.outfile = __dirname + "/_temp/app.server.js" config.options.plugins = [ config.sveltePlugin({ @@ -16,6 +19,11 @@ config.options.plugins = [ dev: (process.argv?.length > 2 ? process.argv[2] : "build") !== "build", }, preprocess: svelteConfig.preprocess, + filterWarnings: (warning) => { + // filter out a11y + if (warning.code.match(/^a11y/)) return false + return true + }, }), config.resolvePlugin, ] diff --git a/frontend/src/admin.ts b/frontend/src/admin.ts new file mode 100644 index 0000000..69acad0 --- /dev/null +++ b/frontend/src/admin.ts @@ -0,0 +1,45 @@ +import type { SvelteComponent } from "svelte" + +function getRenderedElement( + component: typeof SvelteComponent, + options?: { props: { [key: string]: any }; addCss?: string[] }, + nestedElements?: { tagName: string; className?: string }[] +) { + const el = document.createElement("div") + el.attachShadow({ mode: "open" }) + + const body = document.createElement("body") + + // build nested divs with css classes + let target: HTMLElement = body + nestedElements?.forEach((e) => { + const newElement = document.createElement(e.tagName) + if (e.className) { + newElement.className = e.className + } + target.appendChild(newElement) + target = newElement + }) + + el.shadowRoot.appendChild(body) + + options?.addCss?.forEach((css) => { + const link = document.createElement("link") + link.rel = "stylesheet" + link.href = css + link.type = "text/css" + el.shadowRoot.appendChild(link) + }) + + new component({ + target: target, + props: options?.props, + }) + + return el +} + +export { + getRenderedElement, + // pass also required svelte components here +} diff --git a/frontend/src/api.ts b/frontend/src/api.ts new file mode 100644 index 0000000..b7093fa --- /dev/null +++ b/frontend/src/api.ts @@ -0,0 +1,160 @@ +import { get } from "svelte/store" +import { apiRequest, obj2str } from "../../api/hooks/lib/ssr" +import * as sentry from "./sentry" +import { apiBaseOverride } from "./lib/store" + +// fetch polyfill +// [MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com/) +const _f = function (url: string, options?: { [key: string]: any }) { + if (typeof XMLHttpRequest === "undefined") { + return Promise.resolve(null) + } + + options = options || {} + return new Promise((resolve, reject) => { + const request = new XMLHttpRequest() + const keys: string[] = [] + // @ts-ignore + const all = [] + const headers = {} + + const response = () => ({ + ok: ((request.status / 100) | 0) == 2, // 200-299 + statusText: request.statusText, + status: request.status, + url: request.responseURL, + text: () => Promise.resolve(request.responseText), + json: () => Promise.resolve(request.responseText).then(JSON.parse), + blob: () => Promise.resolve(new Blob([request.response])), + clone: response, + headers: { + // @ts-ignore + keys: () => keys, + // @ts-ignore + entries: () => all, + // @ts-ignore + get: (n) => headers[n.toLowerCase()], + // @ts-ignore + has: (n) => n.toLowerCase() in headers, + }, + }) + + request.open(options.method || "get", url, true) + + request.onload = () => { + request + .getAllResponseHeaders() + // @ts-ignore + .replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, (m, key, value) => { + keys.push((key = key.toLowerCase())) + all.push([key, value]) + // @ts-ignore + headers[key] = headers[key] ? `${headers[key]},${value}` : value + }) + resolve(response()) + } + + request.onerror = reject + + request.withCredentials = options.credentials == "include" + + for (const i in options.headers) { + request.setRequestHeader(i, options.headers[i]) + } + + request.send(options.body || null) + }) +} + +// fetch must be declared after sentry import to get the hijacked fetch +// @ts-ignore +export const _fetch: typeof fetch = + typeof fetch === "undefined" ? (typeof window === "undefined" ? _f : window.fetch || _f) : fetch + +export const api = async ( + endpoint: string, + options?: ApiOptions, + body?: any +): Promise<{ data: T; count: number } | any> => { + const _apiBaseOverride = get(apiBaseOverride) || "" + let data = await apiRequest(_apiBaseOverride + endpoint, options, body, sentry, _fetch) + // @ts-ignore + // console.log(data, "data") + return data +} + +const cache: { + [key: string]: { + expire: number + data: any + } +} = {} + +type CollectionNameT = "medialib" | "content" | "product" + +type EntryTypeSwitch = T extends "medialib" + ? MedialibEntry + : T extends "content" + ? ContentEntry + : T extends "product" + ? ProductEntry + : never + +export async function getDBEntries( + collectionName: T, + filter?: { [key: string]: any }, + sort: string = "sort", + limit?: number, + offset?: number, + projection?: string, + params?: { [key: string]: string } +): Promise[]> { + const c = await api[]>(collectionName, { + filter, + sort, + limit, + offset, + projection, + params, + }) + return c.data +} + +export async function getCachedEntries( + collectionName: T, + filter?: { [key: string]: any }, + sort: string = "sort", + limit?: number, + offset?: number, + projection?: string, + params?: { [key: string]: string } +): Promise[]> { + const filterStr = obj2str({ collectionName, filter, sort, limit, offset, projection, params }) + if (cache[filterStr] && cache[filterStr].expire >= Date.now()) { + return cache[filterStr].data + } + const entries = await getDBEntries(collectionName, filter, sort, limit, offset, projection, params) + // expire in 1h + cache[filterStr] = { expire: Date.now() + 1000 * 60 * 60, data: entries } + return entries +} + +export async function getDBEntry( + collectionName: T, + filter: { [key: string]: any }, + params?: { [key: string]: string } +) { + return (await getDBEntries(collectionName, filter, "_id", null, null, null, params))?.[0] +} + +export async function getCachedEntry( + collectionName: T, + filter: { [key: string]: any }, + params?: { [key: string]: string } +) { + return (await getCachedEntries(collectionName, filter, "_id", null, null, null, params))?.[0] +} + +export async function postDBEntry(collectionName: T, entry: EntryTypeSwitch) { + return api>(collectionName, { method: "POST" }, entry) +} diff --git a/svelte.config.js b/svelte.config.js index e0fae66..f864b63 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -9,9 +9,6 @@ module.exports = { sveltePreprocess({ sourceMap: true, typescript: false, - /* scss: { - includePaths: ["src/theme"], - }, */ }), ], } diff --git a/types/global.d.ts b/types/global.d.ts index 93fdbba..885e139 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -41,3 +41,18 @@ interface FileField { type: string size: number } + +interface MedialibEntry { + id: string + // ... +} + +interface ContentEntry { + id: string + // ... +} + +interface ProductEntry { + id: string + // ... +}