diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..d597e72
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,12 @@
+# JWT_SECRET=thisisastring # use if you change from simple bearer to JWT token auth
+BEARER_TOKEN=thisisastring
+
+API_PORT=9230
+
+DB_HOST=db
+DB_PORT=3306
+# DB_PORT=3306
+DB_ROOT_PASSWORD=thisisanotherstring
+DB_DATABASE=17thCoreData
+DB_USER=apiuser
+DB_PASSWORD=thisisathirdstring
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bd62eae
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,135 @@
+/mysql/**
+!/mysql/.gitkeep
+
+
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
\ No newline at end of file
diff --git a/17th-web/.eslintrc.cjs b/17th-web/.eslintrc.cjs
new file mode 100644
index 0000000..b64731a
--- /dev/null
+++ b/17th-web/.eslintrc.cjs
@@ -0,0 +1,14 @@
+/* eslint-env node */
+require('@rushstack/eslint-patch/modern-module-resolution')
+
+module.exports = {
+ root: true,
+ 'extends': [
+ 'plugin:vue/vue3-essential',
+ 'eslint:recommended',
+ '@vue/eslint-config-prettier/skip-formatting'
+ ],
+ parserOptions: {
+ ecmaVersion: 'latest'
+ }
+}
diff --git a/17th-web/.gitignore b/17th-web/.gitignore
new file mode 100644
index 0000000..38adffa
--- /dev/null
+++ b/17th-web/.gitignore
@@ -0,0 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/17th-web/.prettierrc.json b/17th-web/.prettierrc.json
new file mode 100644
index 0000000..66e2335
--- /dev/null
+++ b/17th-web/.prettierrc.json
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://json.schemastore.org/prettierrc",
+ "semi": false,
+ "tabWidth": 2,
+ "singleQuote": true,
+ "printWidth": 100,
+ "trailingComma": "none"
+}
\ No newline at end of file
diff --git a/17th-web/.vscode/extensions.json b/17th-web/.vscode/extensions.json
new file mode 100644
index 0000000..c0a6e5a
--- /dev/null
+++ b/17th-web/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}
diff --git a/17th-web/README.md b/17th-web/README.md
new file mode 100644
index 0000000..282890a
--- /dev/null
+++ b/17th-web/README.md
@@ -0,0 +1,35 @@
+# 17th-web
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```
diff --git a/17th-web/index.html b/17th-web/index.html
new file mode 100644
index 0000000..e9a1937
--- /dev/null
+++ b/17th-web/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+ 17th Info Site
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/package-lock.json b/17th-web/package-lock.json
new file mode 100644
index 0000000..dbc5b28
--- /dev/null
+++ b/17th-web/package-lock.json
@@ -0,0 +1,3522 @@
+{
+ "name": "17th-web",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "17th-web",
+ "version": "0.0.0",
+ "dependencies": {
+ "@headlessui/vue": "^1.7.12",
+ "@heroicons/vue": "^2.0.16",
+ "axios": "^1.3.4",
+ "body-parser": "^1.20.2",
+ "mysql2": "^3.2.0",
+ "pinia": "^2.0.32",
+ "sequelize": "^6.29.3",
+ "vue": "^3.2.47",
+ "vue-router": "^4.1.6",
+ "vuetify": "^3.1.10"
+ },
+ "devDependencies": {
+ "@mdi/font": "^7.2.96",
+ "@rushstack/eslint-patch": "^1.2.0",
+ "@vitejs/plugin-vue": "^4.0.0",
+ "@vue/eslint-config-prettier": "^7.1.0",
+ "autoprefixer": "^10.4.14",
+ "eslint": "^8.34.0",
+ "eslint-plugin-vue": "^9.9.0",
+ "postcss": "^8.4.21",
+ "prettier": "^2.8.4",
+ "tailwindcss": "^3.2.7",
+ "vite": "^4.1.4"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.21.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz",
+ "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.12.tgz",
+ "integrity": "sha512-E/sgkvwoIfj4aMAPL2e35VnUJspzVYl7+M1B2cqeubdBhADV4uPon0KCc8p2G+LqSJ6i8ocYPCqY3A4GGq0zkQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.12.tgz",
+ "integrity": "sha512-WQ9p5oiXXYJ33F2EkE3r0FRDFVpEdcDiwNX3u7Xaibxfx6vQE0Sb8ytrfQsA5WO6kDn6mDfKLh6KrPBjvkk7xA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.12.tgz",
+ "integrity": "sha512-m4OsaCr5gT+se25rFPHKQXARMyAehHTQAz4XX1Vk3d27VtqiX0ALMBPoXZsGaB6JYryCLfgGwUslMqTfqeLU0w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.12.tgz",
+ "integrity": "sha512-O3GCZghRIx+RAN0NDPhyyhRgwa19MoKlzGonIb5hgTj78krqp9XZbYCvFr9N1eUxg0ZQEpiiZ4QvsOQwBpP+lg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.12.tgz",
+ "integrity": "sha512-5D48jM3tW27h1qjaD9UNRuN+4v0zvksqZSPZqeSWggfMlsVdAhH3pwSfQIFJwcs9QJ9BRibPS4ViZgs3d2wsCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.12.tgz",
+ "integrity": "sha512-OWvHzmLNTdF1erSvrfoEBGlN94IE6vCEaGEkEH29uo/VoONqPnoDFfShi41Ew+yKimx4vrmmAJEGNoyyP+OgOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.12.tgz",
+ "integrity": "sha512-A0Xg5CZv8MU9xh4a+7NUpi5VHBKh1RaGJKqjxe4KG87X+mTjDE6ZvlJqpWoeJxgfXHT7IMP9tDFu7IZ03OtJAw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.12.tgz",
+ "integrity": "sha512-WsHyJ7b7vzHdJ1fv67Yf++2dz3D726oO3QCu8iNYik4fb5YuuReOI9OtA+n7Mk0xyQivNTPbl181s+5oZ38gyA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.12.tgz",
+ "integrity": "sha512-cK3AjkEc+8v8YG02hYLQIQlOznW+v9N+OI9BAFuyqkfQFR+DnDLhEM5N8QRxAUz99cJTo1rLNXqRrvY15gbQUg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.12.tgz",
+ "integrity": "sha512-jdOBXJqcgHlah/nYHnj3Hrnl9l63RjtQ4vn9+bohjQPI2QafASB5MtHAoEv0JQHVb/xYQTFOeuHnNYE1zF7tYw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.12.tgz",
+ "integrity": "sha512-GTOEtj8h9qPKXCyiBBnHconSCV9LwFyx/gv3Phw0pa25qPYjVuuGZ4Dk14bGCfGX3qKF0+ceeQvwmtI+aYBbVA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.12.tgz",
+ "integrity": "sha512-o8CIhfBwKcxmEENOH9RwmUejs5jFiNoDw7YgS0EJTF6kgPgcqLFjgoc5kDey5cMHRVCIWc6kK2ShUePOcc7RbA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.12.tgz",
+ "integrity": "sha512-biMLH6NR/GR4z+ap0oJYb877LdBpGac8KfZoEnDiBKd7MD/xt8eaw1SFfYRUeMVx519kVkAOL2GExdFmYnZx3A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.12.tgz",
+ "integrity": "sha512-jkphYUiO38wZGeWlfIBMB72auOllNA2sLfiZPGDtOBb1ELN8lmqBrlMiucgL8awBw1zBXN69PmZM6g4yTX84TA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.12.tgz",
+ "integrity": "sha512-j3ucLdeY9HBcvODhCY4b+Ds3hWGO8t+SAidtmWu/ukfLLG/oYDMaA+dnugTVAg5fnUOGNbIYL9TOjhWgQB8W5g==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.12.tgz",
+ "integrity": "sha512-uo5JL3cgaEGotaqSaJdRfFNSCUJOIliKLnDGWaVCgIKkHxwhYMm95pfMbWZ9l7GeW9kDg0tSxcy9NYdEtjwwmA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.12.tgz",
+ "integrity": "sha512-DNdoRg8JX+gGsbqt2gPgkgb00mqOgOO27KnrWZtdABl6yWTST30aibGJ6geBq3WM2TIeW6COs5AScnC7GwtGPg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.12.tgz",
+ "integrity": "sha512-aVsENlr7B64w8I1lhHShND5o8cW6sB9n9MUtLumFlPhG3elhNWtE7M1TFpj3m7lT3sKQUMkGFjTQBrvDDO1YWA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.12.tgz",
+ "integrity": "sha512-qbHGVQdKSwi0JQJuZznS4SyY27tYXYF0mrgthbxXrZI3AHKuRvU+Eqbg/F0rmLDpW/jkIZBlCO1XfHUBMNJ1pg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.12.tgz",
+ "integrity": "sha512-zsCp8Ql+96xXTVTmm6ffvoTSZSV2B/LzzkUXAY33F/76EajNw1m+jZ9zPfNJlJ3Rh4EzOszNDHsmG/fZOhtqDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.12.tgz",
+ "integrity": "sha512-FfrFjR4id7wcFYOdqbDfDET3tjxCozUgbqdkOABsSFzoZGFC92UK7mg4JKRc/B3NNEf1s2WHxJ7VfTdVDPN3ng==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.12.tgz",
+ "integrity": "sha512-JOOxw49BVZx2/5tW3FqkdjSD/5gXYeVGPDcB0lvap0gLQshkh1Nyel1QazC+wNxus3xPlsYAgqU1BUmrmCvWtw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz",
+ "integrity": "sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz",
+ "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz",
+ "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.5.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz",
+ "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@headlessui/vue": {
+ "version": "1.7.12",
+ "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.12.tgz",
+ "integrity": "sha512-IV0k1I2I8Bj37HljFF231Y9cpldfiucf+inMCxA/VPoQT6UTxo0N/rb78CrogBxXNfsPxKmz3y/nlv+eRz6zvg==",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/@heroicons/vue": {
+ "version": "2.0.16",
+ "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.0.16.tgz",
+ "integrity": "sha512-saVAExLlGu6epqmS543qeWd/UOJUxpTJyK9aXeFGqS3VIW8X7yyJQmUGT40A38gBEAxMOdTdH9jFSh8sjPFM7A==",
+ "peerDependencies": {
+ "vue": ">= 3"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
+ "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@mdi/font": {
+ "version": "7.2.96",
+ "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.2.96.tgz",
+ "integrity": "sha512-e//lmkmpFUMZKhmCY9zdjRe4zNXfbOIJnn6xveHbaV2kSw5aJ5dLXUxcRt1Gxfi7ZYpFLUWlkG2MGSFAiqAu7w==",
+ "dev": true
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rushstack/eslint-patch": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
+ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==",
+ "dev": true
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
+ "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
+ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.15.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
+ "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw=="
+ },
+ "node_modules/@types/validator": {
+ "version": "13.7.14",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.14.tgz",
+ "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g=="
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.1.0.tgz",
+ "integrity": "sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ==",
+ "dev": true,
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
+ "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==",
+ "dependencies": {
+ "@babel/parser": "^7.16.4",
+ "@vue/shared": "3.2.47",
+ "estree-walker": "^2.0.2",
+ "source-map": "^0.6.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz",
+ "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==",
+ "dependencies": {
+ "@vue/compiler-core": "3.2.47",
+ "@vue/shared": "3.2.47"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz",
+ "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==",
+ "dependencies": {
+ "@babel/parser": "^7.16.4",
+ "@vue/compiler-core": "3.2.47",
+ "@vue/compiler-dom": "3.2.47",
+ "@vue/compiler-ssr": "3.2.47",
+ "@vue/reactivity-transform": "3.2.47",
+ "@vue/shared": "3.2.47",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.25.7",
+ "postcss": "^8.1.10",
+ "source-map": "^0.6.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz",
+ "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.2.47",
+ "@vue/shared": "3.2.47"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz",
+ "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
+ },
+ "node_modules/@vue/eslint-config-prettier": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz",
+ "integrity": "sha512-Pv/lVr0bAzSIHLd9iz0KnvAr4GKyCEl+h52bc4e5yWuDVtLgFwycF7nrbWTAQAS+FU6q1geVd07lc6EWfJiWKQ==",
+ "dev": true,
+ "dependencies": {
+ "eslint-config-prettier": "^8.3.0",
+ "eslint-plugin-prettier": "^4.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">= 7.28.0",
+ "prettier": ">= 2.0.0"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz",
+ "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==",
+ "dependencies": {
+ "@vue/shared": "3.2.47"
+ }
+ },
+ "node_modules/@vue/reactivity-transform": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz",
+ "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==",
+ "dependencies": {
+ "@babel/parser": "^7.16.4",
+ "@vue/compiler-core": "3.2.47",
+ "@vue/shared": "3.2.47",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.25.7"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz",
+ "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==",
+ "dependencies": {
+ "@vue/reactivity": "3.2.47",
+ "@vue/shared": "3.2.47"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz",
+ "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==",
+ "dependencies": {
+ "@vue/runtime-core": "3.2.47",
+ "@vue/shared": "3.2.47",
+ "csstype": "^2.6.8"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz",
+ "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.2.47",
+ "@vue/shared": "3.2.47"
+ },
+ "peerDependencies": {
+ "vue": "3.2.47"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
+ "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
+ },
+ "node_modules/acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-node": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz",
+ "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^7.0.0",
+ "acorn-walk": "^7.0.0",
+ "xtend": "^4.0.2"
+ }
+ },
+ "node_modules/acorn-node/node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
+ "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.14",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
+ "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ }
+ ],
+ "dependencies": {
+ "browserslist": "^4.21.5",
+ "caniuse-lite": "^1.0.30001464",
+ "fraction.js": "^4.2.0",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
+ "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.21.5",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
+ "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001449",
+ "electron-to-chromium": "^1.4.284",
+ "node-releases": "^2.0.8",
+ "update-browserslist-db": "^1.0.10"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001468",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001468.tgz",
+ "integrity": "sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "2.6.21",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
+ "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/defined": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz",
+ "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detective": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz",
+ "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==",
+ "dev": true,
+ "dependencies": {
+ "acorn-node": "^1.8.2",
+ "defined": "^1.0.0",
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "detective": "bin/detective.js"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dottie": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.3.tgz",
+ "integrity": "sha512-4liA0PuRkZWQFQjwBypdxPfZaRWiv5tkhMXY2hzsa2pNf5s7U3m9cwUchfNKe8wZQxdGPQQzO6Rm2uGe0rvohQ=="
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.333",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.333.tgz",
+ "integrity": "sha512-YyE8+GKyGtPEP1/kpvqsdhD6rA/TP1DUFDN4uiU/YI52NzDxmwHkEb3qjId8hLBa5siJvG0sfC3O66501jMruQ==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.17.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.12.tgz",
+ "integrity": "sha512-bX/zHl7Gn2CpQwcMtRogTTBf9l1nl+H6R8nUbjk+RuKqAE3+8FDulLA+pHvX7aA7Xe07Iwa+CWvy9I8Y2qqPKQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.17.12",
+ "@esbuild/android-arm64": "0.17.12",
+ "@esbuild/android-x64": "0.17.12",
+ "@esbuild/darwin-arm64": "0.17.12",
+ "@esbuild/darwin-x64": "0.17.12",
+ "@esbuild/freebsd-arm64": "0.17.12",
+ "@esbuild/freebsd-x64": "0.17.12",
+ "@esbuild/linux-arm": "0.17.12",
+ "@esbuild/linux-arm64": "0.17.12",
+ "@esbuild/linux-ia32": "0.17.12",
+ "@esbuild/linux-loong64": "0.17.12",
+ "@esbuild/linux-mips64el": "0.17.12",
+ "@esbuild/linux-ppc64": "0.17.12",
+ "@esbuild/linux-riscv64": "0.17.12",
+ "@esbuild/linux-s390x": "0.17.12",
+ "@esbuild/linux-x64": "0.17.12",
+ "@esbuild/netbsd-x64": "0.17.12",
+ "@esbuild/openbsd-x64": "0.17.12",
+ "@esbuild/sunos-x64": "0.17.12",
+ "@esbuild/win32-arm64": "0.17.12",
+ "@esbuild/win32-ia32": "0.17.12",
+ "@esbuild/win32-x64": "0.17.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.36.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz",
+ "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@eslint/eslintrc": "^2.0.1",
+ "@eslint/js": "8.36.0",
+ "@humanwhocodes/config-array": "^0.11.8",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.5.0",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.7.0.tgz",
+ "integrity": "sha512-HHVXLSlVUhMSmyW4ZzEuvjpwqamgmlfkutD53cYXLikh4pt/modINRcCIApJ84czDxM4GZInwUrromsDdTImTA==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
+ "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.28.0",
+ "prettier": ">=2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-vue": {
+ "version": "9.9.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.9.0.tgz",
+ "integrity": "sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==",
+ "dev": true,
+ "dependencies": {
+ "eslint-utils": "^3.0.0",
+ "natural-compare": "^1.4.0",
+ "nth-check": "^2.0.1",
+ "postcss-selector-parser": "^6.0.9",
+ "semver": "^7.3.5",
+ "vue-eslint-parser": "^9.0.1",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz",
+ "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-diff": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
+ "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://www.patreon.com/infusion"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
+ "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==",
+ "dev": true,
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflection": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz",
+ "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==",
+ "engines": [
+ "node >= 0.4.0"
+ ]
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/js-sdsl": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
+ "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/long": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+ "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.41",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz",
+ "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==",
+ "dependencies": {
+ "moment": "^2.29.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/mysql2": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.2.0.tgz",
+ "integrity": "sha512-0Vn6a9WSrq6fWwvPgrvIwnOCldiEcgbzapVRDAtDZ4cMTxN7pnGqCTx8EG32S/NYXl6AXkdO+9hV1tSIi/LigA==",
+ "dependencies": {
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.6.3",
+ "long": "^5.2.1",
+ "lru-cache": "^7.14.1",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
+ "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
+ "dependencies": {
+ "lru-cache": "^7.14.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/named-placeholders/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
+ "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
+ "dev": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
+ "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "2.0.33",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.33.tgz",
+ "integrity": "sha512-HOj1yVV2itw6rNIrR2f7+MirGNxhORjrULL8GWgRwXsGSvEqIQ+SE0MYt6cwtpegzCda3i+rVTZM+AM7CG+kRg==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.5.0",
+ "vue-demi": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.4.0",
+ "typescript": ">=4.4.4",
+ "vue": "^2.6.14 || ^3.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pinia/node_modules/vue-demi": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
+ "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
+ "integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
+ "dev": true,
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "dev": true,
+ "dependencies": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^1.10.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
+ "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
+ "dev": true,
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.10"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.11",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
+ "integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "2.8.4",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
+ "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/retry-as-promised": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz",
+ "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA=="
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "3.19.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz",
+ "integrity": "sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=14.18.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/sass": {
+ "version": "1.59.3",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.59.3.tgz",
+ "integrity": "sha512-QCq98N3hX1jfTCoUAsF3eyGuXLsY7BCnCEg9qAact94Yc21npG2/mVOqoDvE0fCbWDqiM4WlcJQla0gWG2YlxQ==",
+ "dev": true,
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
+ },
+ "node_modules/sequelize": {
+ "version": "6.29.3",
+ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.29.3.tgz",
+ "integrity": "sha512-iLbrN//Eh18zXIlNEUNQx7lk5R+SF39m+66bnrT3x8WB8sbxMH2hF4vw8RIa9ZzB1+c94rclMv/i8fngXmb/4A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sequelize"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.1.7",
+ "@types/validator": "^13.7.1",
+ "debug": "^4.3.3",
+ "dottie": "^2.0.2",
+ "inflection": "^1.13.2",
+ "lodash": "^4.17.21",
+ "moment": "^2.29.1",
+ "moment-timezone": "^0.5.35",
+ "pg-connection-string": "^2.5.0",
+ "retry-as-promised": "^7.0.3",
+ "semver": "^7.3.5",
+ "sequelize-pool": "^7.1.0",
+ "toposort-class": "^1.0.1",
+ "uuid": "^8.3.2",
+ "validator": "^13.7.0",
+ "wkx": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ibm_db": {
+ "optional": true
+ },
+ "mariadb": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "oracledb": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "pg-hstore": {
+ "optional": true
+ },
+ "snowflake-sdk": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ },
+ "tedious": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sequelize-pool": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz",
+ "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "deprecated": "Please use @jridgewell/sourcemap-codec instead"
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.7.tgz",
+ "integrity": "sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==",
+ "dev": true,
+ "dependencies": {
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "color-name": "^1.1.4",
+ "detective": "^5.2.1",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.2.12",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "lilconfig": "^2.0.6",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.0.9",
+ "postcss-import": "^14.1.0",
+ "postcss-js": "^4.0.0",
+ "postcss-load-config": "^3.1.4",
+ "postcss-nested": "6.0.0",
+ "postcss-selector-parser": "^6.0.11",
+ "postcss-value-parser": "^4.2.0",
+ "quick-lru": "^5.1.1",
+ "resolve": "^1.22.1"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=12.13.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.9"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/toposort-class": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz",
+ "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg=="
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist-lint": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.9.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz",
+ "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/vite": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.0.tgz",
+ "integrity": "sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.17.5",
+ "postcss": "^8.4.21",
+ "resolve": "^1.22.1",
+ "rollup": "^3.18.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.2.47",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz",
+ "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.2.47",
+ "@vue/compiler-sfc": "3.2.47",
+ "@vue/runtime-dom": "3.2.47",
+ "@vue/server-renderer": "3.2.47",
+ "@vue/shared": "3.2.47"
+ }
+ },
+ "node_modules/vue-eslint-parser": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.1.0.tgz",
+ "integrity": "sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.3.4",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.1",
+ "esquery": "^1.4.0",
+ "lodash": "^4.17.21",
+ "semver": "^7.3.6"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz",
+ "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.4.5"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/vuetify": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.1.10.tgz",
+ "integrity": "sha512-TFugKhjYO+xktnexHcs/vdsqtq+MZk8MDLbSJX9vMsA58LPwO4atdBbWhQ1Pf8q1jPISpxDF6njjsZHPQQsHCA==",
+ "engines": {
+ "node": "^12.20 || >=14.13"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/johnleider"
+ },
+ "peerDependencies": {
+ "vite-plugin-vuetify": "^1.0.0-alpha.12",
+ "vue": "^3.2.0",
+ "vue-i18n": "^9.0.0",
+ "webpack-plugin-vuetify": "^2.0.0-alpha.11"
+ },
+ "peerDependenciesMeta": {
+ "vite-plugin-vuetify": {
+ "optional": true
+ },
+ "vue-i18n": {
+ "optional": true
+ },
+ "webpack-plugin-vuetify": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wkx": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
+ "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/17th-web/package.json b/17th-web/package.json
new file mode 100644
index 0000000..b807b06
--- /dev/null
+++ b/17th-web/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "17th-web",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
+ "format": "prettier --write src/"
+ },
+ "dependencies": {
+ "@headlessui/vue": "^1.7.12",
+ "@heroicons/vue": "^2.0.16",
+ "axios": "^1.3.4",
+ "body-parser": "^1.20.2",
+ "mysql2": "^3.2.0",
+ "pinia": "^2.0.32",
+ "sequelize": "^6.29.3",
+ "vue": "^3.2.47",
+ "vue-router": "^4.1.6",
+ "vuetify": "^3.1.10"
+ },
+ "devDependencies": {
+ "@mdi/font": "^7.2.96",
+ "@rushstack/eslint-patch": "^1.2.0",
+ "@vitejs/plugin-vue": "^4.0.0",
+ "@vue/eslint-config-prettier": "^7.1.0",
+ "autoprefixer": "^10.4.14",
+ "eslint": "^8.34.0",
+ "eslint-plugin-vue": "^9.9.0",
+ "postcss": "^8.4.21",
+ "prettier": "^2.8.4",
+ "tailwindcss": "^3.2.7",
+ "vite": "^4.1.4"
+ }
+}
diff --git a/17th-web/postcss.config.js b/17th-web/postcss.config.js
new file mode 100644
index 0000000..6976b23
--- /dev/null
+++ b/17th-web/postcss.config.js
@@ -0,0 +1,10 @@
+const tailwindcss = require('tailwindcss');
+const autoprefixer = require('autoprefixer');
+
+module.exports = {
+ plugins: {
+ tailwindcss,
+ autoprefixer,
+
+ },
+}
diff --git a/17th-web/public/favicon.ico b/17th-web/public/favicon.ico
new file mode 100644
index 0000000..df36fcf
Binary files /dev/null and b/17th-web/public/favicon.ico differ
diff --git a/17th-web/src/App.vue b/17th-web/src/App.vue
new file mode 100644
index 0000000..3a77b2c
--- /dev/null
+++ b/17th-web/src/App.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17th Ranger Battalion
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/assets/js/imageUtils.js b/17th-web/src/assets/js/imageUtils.js
new file mode 100644
index 0000000..c9f8452
--- /dev/null
+++ b/17th-web/src/assets/js/imageUtils.js
@@ -0,0 +1,63 @@
+export function bufferToBase64 (buf) {
+ var binstr = Array.prototype.map.call(buf, function (ch) {
+ return String.fromCharCode(ch);
+ }).join('');
+ return window.btoa(binstr);
+}
+
+export function base64ToBuffer (base64) {
+ var binstr = window.atob(base64);
+ var buf = new Uint8Array(binstr.length);
+ Array.prototype.forEach.call(binstr, function (ch, i) {
+ buf[i] = ch.charCodeAt(0);
+ });
+ return buf;
+}
+
+export function dataURLToBlob (dataURL) {
+ var BASE64_MARKER = ';base64,';
+ if (dataURL.indexOf(BASE64_MARKER) == -1) {
+ var parts = dataURL.split(',');
+ var contentType = parts[0].split(':')[1];
+ var raw = parts[1];
+
+ return new Blob([raw], { type: contentType });
+ }
+
+ var parts = dataURL.split(BASE64_MARKER);
+ var contentType = parts[0].split(':')[1];
+ var raw = window.atob(parts[1]);
+ var rawLength = raw.length;
+
+ var uInt8Array = new Uint8Array(rawLength);
+
+ for (var i = 0; i < rawLength; ++i) {
+ uInt8Array[i] = raw.charCodeAt(i);
+ }
+
+ return new Blob([uInt8Array], { type: contentType });
+}
+
+export function dataURLToBase64 (dataURL) {
+ var BASE64_MARKER = ';base64,';
+ if (dataURL.indexOf(BASE64_MARKER) == -1) {
+ var parts = dataURL.split(',');
+ var contentType = parts[0].split(':')[1];
+ var raw = parts[1];
+
+ return raw;
+ }
+
+ var parts = dataURL.split(BASE64_MARKER);
+ var contentType = parts[0].split(':')[1];
+ var raw = window.atob(parts[1]);
+ var rawLength = raw.length;
+
+ var uInt8Array = new Uint8Array(rawLength);
+
+ for (var i = 0; i < rawLength; ++i) {
+ uInt8Array[i] = raw.charCodeAt(i);
+ }
+
+ return uInt8Array;
+}
\ No newline at end of file
diff --git a/17th-web/src/assets/logo.svg b/17th-web/src/assets/logo.svg
new file mode 100644
index 0000000..7565660
--- /dev/null
+++ b/17th-web/src/assets/logo.svg
@@ -0,0 +1 @@
+
diff --git a/17th-web/src/components/CoursesList.vue b/17th-web/src/components/CoursesList.vue
new file mode 100644
index 0000000..10bf94a
--- /dev/null
+++ b/17th-web/src/components/CoursesList.vue
@@ -0,0 +1,52 @@
+
+
+
+
Courses
+
+
+
+
+
{{ course.name }}
+
+
+
{{ course.description }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/components/HelloWorld.vue b/17th-web/src/components/HelloWorld.vue
new file mode 100644
index 0000000..0a0988b
--- /dev/null
+++ b/17th-web/src/components/HelloWorld.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
{{ msg }}
+
+ You’ve successfully created a project with
+ Vite +
+ Vue 3 .
+
+
+
+
+
diff --git a/17th-web/src/components/NavBar.vue b/17th-web/src/components/NavBar.vue
new file mode 100644
index 0000000..e37565e
--- /dev/null
+++ b/17th-web/src/components/NavBar.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+ Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/components/TheWelcome.vue b/17th-web/src/components/TheWelcome.vue
new file mode 100644
index 0000000..5e64625
--- /dev/null
+++ b/17th-web/src/components/TheWelcome.vue
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+ Documentation
+
+ Vue’s
+ official documentation
+ provides you with all information you need to get started.
+
+
+
+
+
+
+ Tooling
+
+ This project is served and bundled with
+ Vite . The
+ recommended IDE setup is
+ VSCode +
+ Volar . If
+ you need to test your components and web pages, check out
+ Cypress and
+ Cypress Component Testing .
+
+
+
+ More instructions are available in README.md.
+
+
+
+
+
+
+ Ecosystem
+
+ Get official tools and libraries for your project:
+ Pinia ,
+ Vue Router ,
+ Vue Test Utils , and
+ Vue Dev Tools . If
+ you need more resources, we suggest paying
+ Awesome Vue
+ a visit.
+
+
+
+
+
+
+ Community
+
+ Got stuck? Ask your question on
+ Vue Land , our official
+ Discord server, or
+ StackOverflow . You should also subscribe to
+ our mailing list and follow
+ the official
+ @vuejs
+ twitter account for latest news in the Vue world.
+
+
+
+
+
+
+ Support Vue
+
+ As an independent project, Vue relies on community backing for its sustainability. You can help
+ us by
+ becoming a sponsor .
+
+
diff --git a/17th-web/src/components/WelcomeItem.vue b/17th-web/src/components/WelcomeItem.vue
new file mode 100644
index 0000000..a5eca70
--- /dev/null
+++ b/17th-web/src/components/WelcomeItem.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/17th-web/src/components/icons/IconCommunity.vue b/17th-web/src/components/icons/IconCommunity.vue
new file mode 100644
index 0000000..2dc8b05
--- /dev/null
+++ b/17th-web/src/components/icons/IconCommunity.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/17th-web/src/components/icons/IconDocumentation.vue b/17th-web/src/components/icons/IconDocumentation.vue
new file mode 100644
index 0000000..6d4791c
--- /dev/null
+++ b/17th-web/src/components/icons/IconDocumentation.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/17th-web/src/components/icons/IconEcosystem.vue b/17th-web/src/components/icons/IconEcosystem.vue
new file mode 100644
index 0000000..c3a4f07
--- /dev/null
+++ b/17th-web/src/components/icons/IconEcosystem.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/17th-web/src/components/icons/IconSupport.vue b/17th-web/src/components/icons/IconSupport.vue
new file mode 100644
index 0000000..7452834
--- /dev/null
+++ b/17th-web/src/components/icons/IconSupport.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/17th-web/src/components/icons/IconTooling.vue b/17th-web/src/components/icons/IconTooling.vue
new file mode 100644
index 0000000..660598d
--- /dev/null
+++ b/17th-web/src/components/icons/IconTooling.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/17th-web/src/config.js b/17th-web/src/config.js
new file mode 100644
index 0000000..c1dce43
--- /dev/null
+++ b/17th-web/src/config.js
@@ -0,0 +1,8 @@
+import axios from "axios";
+
+export const httpConfig = axios.create({
+ baseURL: "http://localhost:3001/api",
+ headers: {
+ "Content-type": "application/json"
+ }
+})
\ No newline at end of file
diff --git a/17th-web/src/main.js b/17th-web/src/main.js
new file mode 100644
index 0000000..b61a69a
--- /dev/null
+++ b/17th-web/src/main.js
@@ -0,0 +1,17 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+
+import App from '@/App.vue'
+import router from '@/router'
+
+import '@/style.css';
+
+// Vuetify
+import vuetify from '@/plugins/vuetify'
+
+const app = createApp(App)
+app.use(vuetify)
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')
diff --git a/17th-web/src/plugins/vuetify.js b/17th-web/src/plugins/vuetify.js
new file mode 100644
index 0000000..a341c0e
--- /dev/null
+++ b/17th-web/src/plugins/vuetify.js
@@ -0,0 +1,16 @@
+import '@mdi/font/css/materialdesignicons.css' // Ensure you are using css-loader
+import { createVuetify } from 'vuetify'
+import 'vuetify/styles'
+import * as components from 'vuetify/components'
+import * as directives from 'vuetify/directives'
+
+import { md2 } from 'vuetify/blueprints'
+
+export default createVuetify({
+ blueprint: md2,
+ components,
+ directives,
+ icons: {
+ defaultSet: 'mdi', // This is already the default value - only for display purposes
+ },
+})
\ No newline at end of file
diff --git a/17th-web/src/router/dbRoutes/Course.js b/17th-web/src/router/dbRoutes/Course.js
new file mode 100644
index 0000000..b628e98
--- /dev/null
+++ b/17th-web/src/router/dbRoutes/Course.js
@@ -0,0 +1,17 @@
+export default [
+ {
+ path: '/courses',
+ name: 'courses',
+ component: () => import('@/views/dbViews/CoursesView.vue')
+ },
+ {
+ path: '/courses/:id',
+ name: 'course',
+ component: () => import('@/views/dbViews/CourseView.vue')
+ },
+ {
+ path: '/courses/add',
+ name: 'add-course',
+ component: () => import('@/views/dbViews/AddCourseView.vue')
+ }
+]
\ No newline at end of file
diff --git a/17th-web/src/router/dbRoutes/QualificationCategory.js b/17th-web/src/router/dbRoutes/QualificationCategory.js
new file mode 100644
index 0000000..d8174e7
--- /dev/null
+++ b/17th-web/src/router/dbRoutes/QualificationCategory.js
@@ -0,0 +1,17 @@
+export default [
+ {
+ path: '/qualification-categories',
+ name: 'qualification-categories',
+ component: () => import('@/views/dbViews/QualificationCategoriesView.vue')
+ },
+ {
+ path: '/qualification-categories/:id',
+ name: 'qualification-category',
+ component: () => import('@/views/dbViews/QualificationCategoryView.vue')
+ },
+ {
+ path: '/qualification-categories/add',
+ name: 'add-qualification-category',
+ component: () => import('@/views/dbViews/AddQualificationCategoryView.vue')
+ }
+]
\ No newline at end of file
diff --git a/17th-web/src/router/dbRoutes/Ribbon.js b/17th-web/src/router/dbRoutes/Ribbon.js
new file mode 100644
index 0000000..6077ce3
--- /dev/null
+++ b/17th-web/src/router/dbRoutes/Ribbon.js
@@ -0,0 +1,17 @@
+export default [
+ {
+ path: '/ribbons',
+ name: 'ribbons',
+ component: () => import('@/views/dbViews/RibbonsView.vue')
+ },
+ {
+ path: '/ribbons/:id',
+ name: 'ribbon',
+ component: () => import('@/views/dbViews/RibbonView.vue')
+ },
+ {
+ path: '/ribbons/add',
+ name: 'add-ribbon',
+ component: () => import('@/views/dbViews/AddRibbonView.vue')
+ }
+]
diff --git a/17th-web/src/router/index.js b/17th-web/src/router/index.js
new file mode 100644
index 0000000..766348f
--- /dev/null
+++ b/17th-web/src/router/index.js
@@ -0,0 +1,38 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+import RibbonRoutes from './dbRoutes/Ribbon.js'
+import CourseRoutes from './dbRoutes/Course.js'
+import QualificationCategoryRoutes from './dbRoutes/QualificationCategory.js'
+
+const routes =
+ [
+ {
+ path: '/',
+ name: 'home',
+ component: () => import('@/views/HomeView.vue')
+ },
+ {
+ path: '/about',
+ name: 'about',
+ // route level code-splitting
+ // this generates a separate chunk (About.[hash].js) for this route
+ // which is lazy-loaded when the route is visited.
+ component: () => import('../views/AboutView.vue')
+ },
+ ...RibbonRoutes,
+ ...CourseRoutes,
+ ...QualificationCategoryRoutes
+
+
+
+
+
+
+ ]
+
+const router = createRouter({
+ history: createWebHistory(import.meta.env.BASE_URL),
+ routes
+})
+
+export default router
diff --git a/17th-web/src/services/CourseDataService.js b/17th-web/src/services/CourseDataService.js
new file mode 100644
index 0000000..8aba041
--- /dev/null
+++ b/17th-web/src/services/CourseDataService.js
@@ -0,0 +1,33 @@
+import { httpConfig } from "@/config.js";
+
+class CourseDataService {
+ getAll () {
+ return httpConfig.get("/courses");
+ }
+
+ get (id) {
+ return httpConfig.get(`/courses/${id}`);
+ }
+
+ create (data) {
+ return httpConfig.post("/courses", data);
+ }
+
+ update (id, data) {
+ return httpConfig.put(`/courses/${id}`, data);
+ }
+
+ delete (id) {
+ return httpConfig.delete(`/courses/${id}`);
+ }
+
+ deleteAll () {
+ return httpConfig.delete(`/courses`);
+ }
+
+ findByName (name) {
+ return httpConfig.get(`/courses?name=${name}`);
+ }
+}
+
+export default new CourseDataService();
\ No newline at end of file
diff --git a/17th-web/src/services/QualificationCategoryDataService.js b/17th-web/src/services/QualificationCategoryDataService.js
new file mode 100644
index 0000000..52e3782
--- /dev/null
+++ b/17th-web/src/services/QualificationCategoryDataService.js
@@ -0,0 +1,33 @@
+import { httpConfig } from "@/config.js";
+
+class QualificationCategoryDataService {
+ getAll () {
+ return httpConfig.get("/qualification-categories");
+ }
+
+ get (id) {
+ return httpConfig.get(`/qualification-categories/${id}`);
+ }
+
+ create (data) {
+ return httpConfig.post("/qualification-categories", data);
+ }
+
+ update (id, data) {
+ return httpConfig.put(`/qualification-categories/${id}`, data);
+ }
+
+ delete (id) {
+ return httpConfig.delete(`/qualification-categories/${id}`);
+ }
+
+ deleteAll () {
+ return httpConfig.delete(`/qualification-categories`);
+ }
+
+ findByName (name) {
+ return httpConfig.get(`/qualification-categories?name=${name}`);
+ }
+}
+
+export default new QualificationCategoryDataService();
\ No newline at end of file
diff --git a/17th-web/src/services/RibbonDataService.js b/17th-web/src/services/RibbonDataService.js
new file mode 100644
index 0000000..93ed078
--- /dev/null
+++ b/17th-web/src/services/RibbonDataService.js
@@ -0,0 +1,39 @@
+import { httpConfig } from "@/config.js";
+import axios from "axios";
+
+class RibbonDataService {
+ getAll () {
+ return httpConfig.get("/ribbons");
+ }
+
+ get (id) {
+ return httpConfig.get(`/ribbons/${id}`);
+ }
+
+ create (data) {
+ return httpConfig.post("/ribbons", data);
+ // return axios.post("https://httpbin.org/post", data)
+ }
+
+ update (id, data) {
+ return httpConfig.put(`/ribbons/${id}`, data);
+ }
+
+ delete (id) {
+ return httpConfig.delete(`/ribbons/${id}`);
+ }
+
+ deleteAll () {
+ return httpConfig.delete(`/ribbons`);
+ }
+
+ findByName (name) {
+ return httpConfig.get(`/ribbons?name=${name}`);
+ }
+
+ getCategories () {
+ return httpConfig.get("/qualification-categories");
+ }
+}
+
+export default new RibbonDataService();
\ No newline at end of file
diff --git a/17th-web/src/stores/counter.js b/17th-web/src/stores/counter.js
new file mode 100644
index 0000000..b6757ba
--- /dev/null
+++ b/17th-web/src/stores/counter.js
@@ -0,0 +1,12 @@
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+
+export const useCounterStore = defineStore('counter', () => {
+ const count = ref(0)
+ const doubleCount = computed(() => count.value * 2)
+ function increment() {
+ count.value++
+ }
+
+ return { count, doubleCount, increment }
+})
diff --git a/17th-web/src/style.css b/17th-web/src/style.css
new file mode 100644
index 0000000..454ec73
--- /dev/null
+++ b/17th-web/src/style.css
@@ -0,0 +1,7 @@
+#app {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
\ No newline at end of file
diff --git a/17th-web/src/views/AboutView.vue b/17th-web/src/views/AboutView.vue
new file mode 100644
index 0000000..756ad2a
--- /dev/null
+++ b/17th-web/src/views/AboutView.vue
@@ -0,0 +1,15 @@
+
+
+
This is an about page
+
+
+
+
diff --git a/17th-web/src/views/HomeView.vue b/17th-web/src/views/HomeView.vue
new file mode 100644
index 0000000..6bb706f
--- /dev/null
+++ b/17th-web/src/views/HomeView.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/17th-web/src/views/dbViews/AddCourseView.vue b/17th-web/src/views/dbViews/AddCourseView.vue
new file mode 100644
index 0000000..4a53976
--- /dev/null
+++ b/17th-web/src/views/dbViews/AddCourseView.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/AddQualificationCategoryView.vue b/17th-web/src/views/dbViews/AddQualificationCategoryView.vue
new file mode 100644
index 0000000..0b6f756
--- /dev/null
+++ b/17th-web/src/views/dbViews/AddQualificationCategoryView.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/AddRibbonView.vue b/17th-web/src/views/dbViews/AddRibbonView.vue
new file mode 100644
index 0000000..28d0a78
--- /dev/null
+++ b/17th-web/src/views/dbViews/AddRibbonView.vue
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/CourseView.vue b/17th-web/src/views/dbViews/CourseView.vue
new file mode 100644
index 0000000..0ec5334
--- /dev/null
+++ b/17th-web/src/views/dbViews/CourseView.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
Please click on a Course...
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/CoursesView.vue b/17th-web/src/views/dbViews/CoursesView.vue
new file mode 100644
index 0000000..3c0fc4d
--- /dev/null
+++ b/17th-web/src/views/dbViews/CoursesView.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
Courses List
+
+
+ {{ course.name }}
+
+
+
+
Remove All
+
+
+
+
Course
+
+ Name: {{ currentCourse.name }}
+
+
+ Description: {{ currentCourse.description }}
+
+
+ Status:
+ {{ currentCourse.published ? 'Published' : 'Pending' }}
+
+
+
Edit
+
+
+
+
Please click on a Course...
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/QualificationCategoriesView.vue b/17th-web/src/views/dbViews/QualificationCategoriesView.vue
new file mode 100644
index 0000000..3c0fc4d
--- /dev/null
+++ b/17th-web/src/views/dbViews/QualificationCategoriesView.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
Courses List
+
+
+ {{ course.name }}
+
+
+
+
Remove All
+
+
+
+
Course
+
+ Name: {{ currentCourse.name }}
+
+
+ Description: {{ currentCourse.description }}
+
+
+ Status:
+ {{ currentCourse.published ? 'Published' : 'Pending' }}
+
+
+
Edit
+
+
+
+
Please click on a Course...
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/QualificationCategoryView.vue b/17th-web/src/views/dbViews/QualificationCategoryView.vue
new file mode 100644
index 0000000..0ec5334
--- /dev/null
+++ b/17th-web/src/views/dbViews/QualificationCategoryView.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
Please click on a Course...
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/RibbonView.vue b/17th-web/src/views/dbViews/RibbonView.vue
new file mode 100644
index 0000000..eb96e20
--- /dev/null
+++ b/17th-web/src/views/dbViews/RibbonView.vue
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Delete
+ Reset
+ Save
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/src/views/dbViews/RibbonsView.vue b/17th-web/src/views/dbViews/RibbonsView.vue
new file mode 100644
index 0000000..f60e233
--- /dev/null
+++ b/17th-web/src/views/dbViews/RibbonsView.vue
@@ -0,0 +1,226 @@
+
+
+
+
+ Search and Filter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Ribbon
+
+
+
+
+
+
+
+
+ {{ ribbon.name }}
+ {{ ribbon.footprint.charAt(0).toUpperCase() + ribbon.footprint.slice(1) }}
+
+
+ {{ ribbon.shortname }}
+
+
+ {{ ribbon.description }}
+
+
+
+
+
+ {{ ribbon.footprint }}
+
+
+
+ {{ ribbon.category?.name }}
+
+
+ Edit Ribbon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/17th-web/tailwind.config.js b/17th-web/tailwind.config.js
new file mode 100644
index 0000000..dc9ec90
--- /dev/null
+++ b/17th-web/tailwind.config.js
@@ -0,0 +1,32 @@
+/** @type {import('tailwindcss').Config} */
+const colors = require('tailwindcss/colors')
+
+module.exports = {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx,vue}",
+ ],
+ plugins: [
+ ],
+ theme: {
+ screens: {
+ sm: '480px',
+ md: '768px',
+ lg: '976px',
+ xl: '1440px',
+ },
+ fontFamily: {
+ sans: ['Inter', 'sans-serif'],
+ serif: ['Merriweather', 'serif'],
+ },
+ extend: {
+ spacing: {
+ '128': '32rem',
+ '144': '36rem',
+ },
+ borderRadius: {
+ '4xl': '2rem',
+ }
+ }
+ }
+}
diff --git a/17th-web/vite.config.js b/17th-web/vite.config.js
new file mode 100644
index 0000000..3b440a7
--- /dev/null
+++ b/17th-web/vite.config.js
@@ -0,0 +1,16 @@
+import { fileURLToPath, URL } from 'node:url'
+
+const path = require('path')
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [vue()],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
+ }
+ }
+})
diff --git a/README.md b/README.md
index 2ac9208..191ed35 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,82 @@
-# 17th-Battalion-Tracker
+# 17th Website Project
+## Basic Dev Environment
+
+*Assumes you have Docker and Docker Compose installed (included in Docker Desktop).*
+
+1. Copy `/.env.example` to `/.env` and populate with desired options.
+1. Navigate to the project root in a terminal.
+2. Run `docker compose up -d --build db`
+3. Run `docker compose up -d --build api`
+4. Let it build the images
+
+You will have a SQL server accessible on port 12730 from your host (and publicly if not firewalled) for SQL Workbench access.
+
+The API will launch and be accessible on port 3000 from your host via Postman or a browser.
+
+### Nginx (optional)
+
+*Adjust the listening ports*
+
+Adjust the listening ports in two places:
+ `/nginx/api.conf`: Change the listening port in one or both servers
+ `/docker-compose.yaml`: Change the 'ports' entries in the nginx service. The first part is the host port to bind, the second part is the container port to bind. For example, `9000:3440` would route inbound requests for `localhost:9000` on to the nginx container (and the nginx service) at port 3440.
+
+*Adding Nginx without SSL*
+
+If you want to use Nginx without SSL, comment out the second `server` object in `nginx/api.conf` so you're only handling basic HTTP requests. Run `docker compose up -d --built nginx`.
+
+*Adding Nginx with SSL*
+
+You can use the commands in the other file of `/nginx` to generate self-signed certificates for the domain you're hosting this at. Certbot will remotely check routing for that domain to itself and assign a certificate if valid, based on the well known acme challenge already set up in the `/nginx/api.conf` file.
+
+> IMPORTANT: If you want to do this, you'll need to comment out the second `server` object in `/nginx/api.conf` FIRST so Nginx doesn't crash when it looks for not-yet-existing SSL certs, and can respond on port 9230 (or whatever you've set it to) with the response. Then launch, ensure Nginx is running, then launch a Certbot command to check the domain endpoint.
+
+## Services in Docker Compose
+
+### DB
+
+*MySQL database with relatively default configuration*
+
+- references .env file for SQL root password and other information
+- exposes SQL interface on port 12730 of the host - inbound access still subject to firewall restrictions
+- natively accessible at the unchanged 3306 port for any containers within the same Compose project's default bridge network -- i.e., the api service in this project references the mysql container by hostname 'db', matching the service name
+
+### API
+
+*Node.js-served API using express.js and Sequelize. Documentation in [OpenAPI 3.0 spec](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md).*
+
+- references DB connectivity details & shared secret for authenticating HTTP requests from .env file
+- contains controllers, models, and routes
+ - controllers
+ - defines actions to be taken in response to incoming web requests
+ - includes data processing, database actions, and response actions
+ - models
+ - defines entity models - entity default fields and constraints
+ - routes
+ - pairs API endpoints with controller definitions
+ - junctionModels
+ - a folder for modeling entities that serve explicitly as junction points -- most notably 'event' formats
+
+`db/index.js` imports models and defines relations among them. it exports a 'db' object containing the Sequelize instance as well as all imported models. This is referenced in the primary application, as a **database synchronization is performed at runtime**.
+
+`/index.js` Contains the main application logic and express configuration. It currently uses CORS and a very in-development section, but with basic authentication through standard bearer tokens. a JWT alternative is commented out.
+
+> This main logic also instructs Sequelize to sync the database on launch. **`force: true`, all data will be wiped** as tables are dropped to account for possible changes in the schema definitions. This is a development tool and should be disabled in production.
+
+
+### Nginx and Certbot
+
+Optional nginx instance for serving the API via reverse proxy on an external port of your choosing. This include a certbot container for acquiring a self-signed certificate, and bind mounts can be changed to utilize them to secure the API with SSL.
+
+
+### 17th-web
+
+*This is a Vue instance designed to interface with the API and display the information to users, as well as permit administrators to manipulate the database. This has fallen behind active API development and may no longer work. This can be replaced with AJ's demo site if favored.*
+
+
+## Resources
+
+- [Bootstrap](https://getbootstrap.com/)
+- [Vue 3 CRUD Tutorial](https://www.bezkoder.com/vue-3-crud/)
+- [Node.js & Sequelize (MySQL) Tutorial](https://www.bezkoder.com/node-js-express-sequelize-mysql/)
diff --git a/api/Dockerfile b/api/Dockerfile
new file mode 100644
index 0000000..5cb76e9
--- /dev/null
+++ b/api/Dockerfile
@@ -0,0 +1,15 @@
+# nodejs container
+FROM node:latest
+
+# Create app directory
+WORKDIR /app
+
+# Bundle app source
+# COPY . /app
+
+# Install app dependencies
+# RUN npm install
+
+EXPOSE $API_PORT
+CMD [ "npm", "run", "start" ]
+
diff --git a/api/db/config.js b/api/db/config.js
new file mode 100644
index 0000000..e4964a2
--- /dev/null
+++ b/api/db/config.js
@@ -0,0 +1,19 @@
+// dotenv
+// .config()
+
+module.exports = {
+ HOST: process.env.DB_HOST,
+ PORT: process.env.DB_PORT,
+ // USER: process.env.DB_USER,
+ USER: 'root',
+ // PASSWORD: process.env.DB_PASSWORD,
+ PASSWORD: process.env.DB_ROOT_PASSWORD,
+ DATABASE: process.env.DB_DATABASE,
+ dialect: "mysql",
+ pool: {
+ max: 5,
+ min: 0,
+ acquire: 30000,
+ idle: 10000
+ }
+};
\ No newline at end of file
diff --git a/api/db/controllers/Award.controller.js b/api/db/controllers/Award.controller.js
new file mode 100644
index 0000000..5f677bf
--- /dev/null
+++ b/api/db/controllers/Award.controller.js
@@ -0,0 +1,70 @@
+const db = require("..");
+const Award = db.Award;
+const Op = db.Sequelize.Op;
+
+// Create and Save a new Award
+exports.create = (req, res) => {
+ // Validate
+ if (!req.body) {
+ res.status(400).send({
+ message: "Body content can not be empty!"
+ });
+ return;
+ }
+
+ const awards = []
+
+ if (Array.isArray(req.body)) {
+ awards.push(...req.body)
+ } else {
+ awards.push(req.body)
+ }
+
+ // Save
+ const promises = awards.map(award => {
+ return Award.create(award)
+ });
+
+ // Create
+ const successes = []
+ const failures = []
+ Promise.allSettled(promises)
+ .then(data => {
+ if (data.every(result => result.status === 'fulfilled')) {
+ res.status(201).send({
+ message: "All awards were created successfully.",
+ successes: data.map(result => result.value),
+ failures: [],
+ })
+ return;
+ }
+
+ data.forEach(result => {
+ if (result.status === 'fulfilled') {
+ successes.push(result.value)
+ } else {
+ failures.push(result.reason.errors)
+ }
+ })
+ if (successes.length === 0) {
+ res.status(500).send({
+ message:
+ "Failed to create any Awards.",
+ failures: failures,
+ successes: successes,
+ });
+ return;
+ }
+ res.status(207).send({
+ message: "Some Awards were created successfully.",
+ successes: successes,
+ failures: failures,
+ })
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while creating the Award.",
+ });
+ });
+};
diff --git a/api/db/controllers/Course.controller.js b/api/db/controllers/Course.controller.js
new file mode 100644
index 0000000..1061cb0
--- /dev/null
+++ b/api/db/controllers/Course.controller.js
@@ -0,0 +1,176 @@
+const db = require("../");
+const Course = db.Course;
+const Op = db.Sequelize.Op;
+
+// Create and Save a new Course
+exports.create = (req, res) => {
+ // Validate
+ if (!req.body) {
+ res.status(400).send({
+ message: "Body content can not be empty!"
+ });
+ return;
+ }
+
+ // Create
+ const courses = []
+
+ if (Array.isArray(req.body)) {
+ courses.push(...req.body)
+ } else {
+ courses.push(req.body)
+ }
+
+ // Save
+ const promises = []
+ courses.forEach(course => {
+ promises.push(Course.create(course))
+ });
+
+ const successes = []
+ const failures = []
+ Promise.allSettled(promises)
+ .then(data => {
+ if (data.every(result => result.status === 'fulfilled')) {
+ res.status(201).send({
+ message: "All courses were created successfully.",
+ successes: data.map(result => result.value),
+ failures: [],
+ })
+ return;
+ }
+
+ data.forEach(result => {
+ if (result.status === 'fulfilled') {
+ successes.push(result.value)
+ } else {
+ failures.push(result.reason)
+ }
+ })
+ res.status(207).send({
+ message: "Some courses were created successfully.",
+ successes: successes,
+ failures: failures,
+ })
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while creating the Course.",
+
+ });
+ });
+};
+
+// Retrieve all Courses from the database.
+exports.findAll = (req, res) => {
+ const name = req.query.name;
+ var condition = name ? { name: { [Op.like]: `%${name}%` } } : null;
+
+ Course.findAll({ where: condition, include: ['trainingsHeld'] })
+ .then(data => {
+ res.send(data);
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving courses."
+ });
+ });
+};
+
+// Find a single Course with an id
+exports.findOne = (req, res) => {
+ const id = req.params.id;
+
+ Course.findByPk(id)
+ .then(data => {
+ res.send(data);
+ })
+ .catch(err => {
+ res.status(500).send({
+ message: "Error retrieving Course with id=" + id
+ });
+ });
+};
+
+// Update a Course by the id in the request
+exports.update = (req, res) => {
+ const id = req.params.id;
+
+ Course.update(req.body, {
+ where: { id: id }
+ })
+ .then(num => {
+ if (num == 1) {
+ res.send({
+ message: "Course was updated successfully."
+ });
+ } else {
+ res.send({
+ message: `Cannot update Course with id=${id}. Maybe Course was not found or req.body is empty!`
+ });
+ }
+ })
+ .catch(err => {
+ res.status(500).send({
+ message: "Error updating Course with id=" + id
+ });
+ });
+};
+
+// Delete a Course with the specified id in the request
+exports.delete = (req, res) => {
+ const id = req.params.id;
+
+ Course.destroy({
+ where: { id: id }
+ })
+ .then(num => {
+ if (num == 1) {
+ res.send({
+ message: "Course was deleted successfully!"
+ });
+ } else {
+ res.send({
+ message: `Cannot delete Course with id=${id}. Maybe Course was not found!`
+ });
+ }
+ })
+ .catch(err => {
+ res.status(500).send({
+ message: "Could not delete Course with id=" + id
+ });
+ });
+};
+
+// Delete all Courses from the database.
+exports.deleteAll = (req, res) => {
+ Course.destroy({
+ where: {},
+ truncate: false
+ })
+ .then(nums => {
+ res.send({ message: `${nums} Courses were deleted successfully!` });
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while removing all courses."
+ });
+ });
+};
+
+// Find all published Courses
+exports.findAllPublished = (req, res) => {
+ Course.findAll({ where: { published: true } })
+ .then(data => {
+ res.send(data);
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving courses."
+ });
+ });
+};
\ No newline at end of file
diff --git a/api/db/controllers/Member.controller.js b/api/db/controllers/Member.controller.js
new file mode 100644
index 0000000..e009a73
--- /dev/null
+++ b/api/db/controllers/Member.controller.js
@@ -0,0 +1,100 @@
+const db = require("..");
+const Member = db.Member;
+const Op = db.Sequelize.Op;
+
+// Create and Save a new Member
+exports.create = (req, res) => {
+ // Validate
+ if (!req.body) {
+ res.status(400).send({
+ message: "Body content can not be empty!"
+ });
+ return;
+ }
+
+ // Create
+ const members = []
+
+ if (Array.isArray(req.body)) {
+ members.push(...req.body)
+ } else {
+ members.push(req.body)
+ }
+
+ // Save
+ const promises = members.map(member => {
+ return Member.create(member)
+ .then(memberObj => {
+ if (req.body.rankId) {
+ db.Rank.findByPk(req.body.rankId)
+ .then(rank => {
+ memberObj.setRank(rank)
+ })
+ }
+ if (req.body.statusId) {
+ db.MemberStatus.findByPk(req.body.statusId)
+ .then(status => {
+ memberObj.setStatus(status)
+ })
+ }
+ return memberObj
+ })
+ });
+
+ // Create
+ const successes = []
+ const failures = []
+ Promise.allSettled(promises)
+ .then(data => {
+ if (data.every(result => result.status === 'fulfilled')) {
+ res.status(201).send({
+ message: "All members were created successfully.",
+ successes: data.map(result => result.value),
+ failures: [],
+ })
+ return;
+ }
+
+ data.forEach(result => {
+ if (result.status === 'fulfilled') {
+ successes.push(result.value)
+ } else {
+ failures.push(result.reason.errors)
+ }
+ })
+ if (successes.length === 0) {
+ res.status(500).send({
+ message:
+ "Failed to create any Members.",
+ failures: failures,
+ successes: successes,
+ });
+ return;
+ }
+ res.status(207).send({
+ message: "Some Members were created successfully.",
+ successes: successes,
+ failures: failures,
+ })
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while creating the Member.",
+ });
+ });
+};
+
+// Retrieve all Members from the database.
+exports.findAll = (req, res) => {
+ Member.findAll()
+ .then(data => {
+ res.send(data);
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members."
+ });
+ });
+};
\ No newline at end of file
diff --git a/api/db/controllers/Rank.controller.js b/api/db/controllers/Rank.controller.js
new file mode 100644
index 0000000..fe7b9bc
--- /dev/null
+++ b/api/db/controllers/Rank.controller.js
@@ -0,0 +1,70 @@
+const db = require("..");
+const Rank = db.Rank;
+const Op = db.Sequelize.Op;
+
+// Create and Save a new Rank
+exports.create = (req, res) => {
+ // Validate
+ if (!req.body) {
+ res.status(400).send({
+ message: "Body content can not be empty!"
+ });
+ return;
+ }
+
+ const ranks = []
+
+ if (Array.isArray(req.body)) {
+ ranks.push(...req.body)
+ } else {
+ ranks.push(req.body)
+ }
+
+ // Save
+ const promises = ranks.map(rank => {
+ return Rank.create(rank)
+ });
+
+ // Create
+ const successes = []
+ const failures = []
+ Promise.allSettled(promises)
+ .then(data => {
+ if (data.every(result => result.status === 'fulfilled')) {
+ res.status(201).send({
+ message: "All ranks were created successfully.",
+ successes: data.map(result => result.value),
+ failures: [],
+ })
+ return;
+ }
+
+ data.forEach(result => {
+ if (result.status === 'fulfilled') {
+ successes.push(result.value)
+ } else {
+ failures.push(result.reason.errors)
+ }
+ })
+ if (successes.length === 0) {
+ res.status(500).send({
+ message:
+ "Failed to create any Ranks.",
+ failures: failures,
+ successes: successes,
+ });
+ return;
+ }
+ res.status(207).send({
+ message: "Some Ranks were created successfully.",
+ successes: successes,
+ failures: failures,
+ })
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while creating the Rank.",
+ });
+ });
+};
diff --git a/api/db/index.js b/api/db/index.js
new file mode 100644
index 0000000..6f54913
--- /dev/null
+++ b/api/db/index.js
@@ -0,0 +1,260 @@
+// require dbconfig
+const dbConfig = require("./config.js");
+
+
+// create connection to database
+const Sequelize = require("sequelize");
+const sequelize = new Sequelize(
+ dbConfig.DATABASE,
+ dbConfig.USER,
+ dbConfig.PASSWORD,
+ {
+ host: dbConfig.HOST,
+ port: dbConfig.PORT,
+ dialect: dbConfig.dialect,
+
+ pool: {
+ max: dbConfig.pool.max,
+ min: dbConfig.pool.min,
+ acquire: dbConfig.pool.acquire,
+ idle: dbConfig.pool.idle
+ }
+ }
+);
+
+try {
+ sequelize.authenticate();
+} catch (error) {
+ console.error('Unable to connect to the database:', error);
+ return
+}
+
+
+
+const db = {};
+
+db.Sequelize = Sequelize;
+db.instance = sequelize;
+
+db.Member = db.instance.define("Member", require("./models/Member.model.js"), { paranoid: true })
+db.Award = db.instance.define("Award", require("./models/Award.model.js"), { paranoid: true })
+db.Course = db.instance.define("Course", require("./models/Course.model.js"), { paranoid: true })
+db.Rank = db.instance.define("Rank", require("./models/Rank.model.js"), { paranoid: true })
+
+db.AwardAction = db.instance.define("AwardAction", require("./junctionModels/AwardAction.js"), { paranoid: true })
+db.CourseEventTrainingReport = db.instance.define("CourseEventTrainingReport", require("./junctionModels/CourseEventTrainingReport.model.js"), { paranoid: true })
+db.CourseEvent = db.instance.define("CourseEvent", require("./models/CourseEvent.js"), { paranoid: true })
+
+// Members have ranks
+db.Rank.hasMany(db.Member, {
+ as: "members",
+ foreignKey: "rankId"
+})
+db.Member.belongsTo(db.Rank, {
+ as: "rank",
+ foreignKey: "rankId"
+})
+
+// Members have statuses
+db.MemberStatus = db.instance.define("MemberStatus", require("./models/MemberStatus.model.js"), { paranoid: true })
+db.MemberStatus.hasMany(db.Member, {
+ as: "members",
+ foreignKey: "statusId"
+})
+db.Member.belongsTo(db.MemberStatus, {
+ as: "status",
+ foreignKey: "statusId"
+})
+
+
+// * AWARDS
+// Awards have a creator
+db.Award.belongsTo(db.Member, {
+ as: "createdBy",
+ foreignKey: "createdById"
+})
+db.Member.hasMany(db.Award, {
+ as: "awardsCreated",
+ foreignKey: "createdById"
+})
+// Awards have a last modified by
+db.Award.belongsTo(db.Member, {
+ as: "lastModifiedBy",
+ foreignKey: "lastModifiedById"
+})
+db.Member.hasMany(db.Award, {
+ as: "awardsLastModified",
+ foreignKey: "lastModifiedById"
+})
+// Members are granted awards
+db.Award.belongsToMany(db.Member, {
+ through: db.AwardAction,
+ as: "awardHolders",
+ foreignKey: "recipientId",
+});
+db.Member.belongsToMany(db.Award, {
+ through: db.AwardAction,
+ as: "awardsEarned",
+ foreignKey: "awardId"
+});
+// * AWARDS GRANTED/REVOKED
+// Instances of award grants have a creator
+db.AwardAction.belongsTo(db.Member, {
+ as: "createdBy",
+ foreignKey: "createdById"
+})
+db.Member.hasMany(db.AwardAction, {
+ as: "awardGrantsCreated",
+ foreignKey: "createdById"
+})
+// Instances of award grants have a last modified by
+db.AwardAction.belongsTo(db.Member, {
+ as: "lastModifiedBy",
+ foreignKey: "lastModifiedById"
+})
+db.Member.hasMany(db.AwardAction, {
+ as: "awardGrantsLastModified",
+ foreignKey: "lastModifiedById"
+})
+// Instances of award grants have the acting member
+db.AwardAction.belongsTo(db.Member, {
+ as: "actor",
+ foreignKey: "actorId"
+})
+db.Member.hasMany(db.AwardAction, {
+ as: "awardGrantsGiven",
+ foreignKey: "actorId"
+})
+// Instances of award grants have the recipient
+db.AwardAction.belongsTo(db.Member, {
+ as: "recipient",
+ foreignKey: "recipientId"
+})
+db.Member.hasMany(db.AwardAction, {
+ as: "awardGrantsReceived",
+ foreignKey: "recipientId"
+})
+// Instances of award grants have the award
+db.AwardAction.belongsTo(db.Award, {
+ as: "award",
+ foreignKey: "awardId"
+})
+db.Award.hasMany(db.AwardAction, {
+ as: "awardGrants",
+ foreignKey: "awardId"
+})
+
+
+
+// * COURSE EVENTS
+// Events have a creator
+db.CourseEvent.belongsTo(db.Member, {
+ as: "createdBy",
+ foreignKey: "createdById"
+})
+db.Member.hasMany(db.CourseEvent, {
+ as: "courseEventsCreated",
+ foreignKey: "createdById"
+})
+// Events have a last modified by
+db.CourseEvent.belongsTo(db.Member, {
+ as: "lastModifiedBy",
+ foreignKey: "lastModifiedById"
+})
+db.Member.hasMany(db.CourseEvent, {
+ as: "courseEventsLastModified",
+ foreignKey: "lastModifiedById"
+})
+// Events have a course that's taught
+db.CourseEvent.belongsTo(db.Course, {
+ as: "courseTaught",
+ foreignKey: "courseTaughtId"
+})
+db.Course.hasMany(db.CourseEvent, {
+ as: "trainingsHeld",
+ foreignKey: "courseTaughtId"
+})
+// Events have one or more trainer
+db.CourseEvent.belongsToMany(db.Member, {
+ through: "CourseEventsTrainers",
+ as: "trainers",
+ foreignKey: "trainerId"
+})
+db.Member.belongsToMany(db.CourseEvent, {
+ through: "CourseEventsTrainers",
+ as: "courseEventsTaught",
+ foreignKey: "courseEventId"
+})
+// Events have one or more observer
+db.CourseEvent.belongsToMany(db.Member, {
+ through: "CourseEventsObservers",
+ as: "observers",
+ foreignKey: "courseEventId"
+})
+db.Member.belongsToMany(db.CourseEvent, {
+ through: "CourseEventsObservers",
+ as: "courseEventsObserved",
+ foreignKey: "observerId"
+})
+// Events have one or more attendees, each of which passed or did not
+db.CourseEvent.belongsToMany(db.Member, {
+ through: db.CourseEventTrainingReport,
+ as: "attendees",
+ foreignKey: "attendeeId"
+});
+db.Member.belongsToMany(db.CourseEvent, {
+ through: db.CourseEventTrainingReport,
+ as: "courseEventsAttended",
+ foreignKey: "courseEventId"
+});
+
+
+// * COURSES
+// Courses have a creator
+db.Course.belongsTo(db.Member, {
+ as: "createdBy",
+ foreignKey: "createdById"
+})
+db.Member.hasMany(db.Course, {
+ as: "coursesCreated",
+ foreignKey: "createdById"
+})
+// Courses have a last modified by
+db.Course.belongsTo(db.Member, {
+ as: "lastModifiedBy",
+ foreignKey: "lastModifiedById"
+})
+db.Member.hasMany(db.Course, {
+ as: "coursesLastModified",
+ foreignKey: "lastModifiedById"
+})
+// Courses have SMEs
+db.Course.belongsToMany(db.Member, {
+ through: "CoursesSME",
+ as: "sme",
+ foreignKey: "smeId"
+})
+db.Member.belongsToMany(db.Course, {
+ through: "CoursesSME",
+ as: "coursesSMEFor",
+ foreignKey: "courseId"
+})
+
+
+
+// Courses belong to award paths
+db.Award.belongsToMany(db.Course, {
+ through: "CoursesAwards",
+ as: "coursesRequired",
+ foreignKey: "courseId"
+});
+// Awards have pre-requisite courses
+db.Course.belongsToMany(db.Award, {
+ through: "CoursesAwards",
+ as: "possibleAwards",
+ foreignKey: "awardId"
+});
+
+
+
+module.exports = db;
\ No newline at end of file
diff --git a/api/db/junctionModels/AwardAction.js b/api/db/junctionModels/AwardAction.js
new file mode 100644
index 0000000..7d004b1
--- /dev/null
+++ b/api/db/junctionModels/AwardAction.js
@@ -0,0 +1,34 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+ actionDate: {
+ type: Sequelize.DataTypes.DATEONLY,
+ allowNull: false,
+ defaultValue: Sequelize.DataTypes.NOW
+ },
+ isGrantEvent: {
+ type: Sequelize.DataTypes.BOOLEAN,
+ allowNull: true,
+ defaultValue: true
+ },
+ actorId: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ recipientId: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ awardId: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ createdById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ lastModifiedById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+}
\ No newline at end of file
diff --git a/api/db/junctionModels/courseEventTrainingReport.model.js b/api/db/junctionModels/courseEventTrainingReport.model.js
new file mode 100644
index 0000000..0c32ea2
--- /dev/null
+++ b/api/db/junctionModels/courseEventTrainingReport.model.js
@@ -0,0 +1,21 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+ attendeeId: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ courseEventId: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ passed: {
+ type: Sequelize.DataTypes.BOOLEAN,
+ allowNull: false,
+ defaultValue: false
+ },
+ notes: {
+ type: Sequelize.DataTypes.STRING(500),
+ allowNull: true,
+ }
+}
\ No newline at end of file
diff --git a/api/db/models/Award.model.js b/api/db/models/Award.model.js
new file mode 100644
index 0000000..7a9287b
--- /dev/null
+++ b/api/db/models/Award.model.js
@@ -0,0 +1,37 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+ name: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false,
+ },
+ shortName: {
+ type: Sequelize.DataTypes.STRING(70),
+ allowNull: false
+ },
+ description: {
+ type: Sequelize.DataTypes.STRING(1000),
+ allowNull: true
+ },
+ category: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false
+ },
+ imageUrl: {
+ type: Sequelize.DataTypes.STRING,
+ allowNull: true,
+ isUrl: true
+ },
+ footprint: {
+ type: Sequelize.DataTypes.STRING(45),
+ allowNull: false
+ },
+ createdById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ lastModifiedById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+};
\ No newline at end of file
diff --git a/api/db/models/Course.model.js b/api/db/models/Course.model.js
new file mode 100644
index 0000000..ea021ed
--- /dev/null
+++ b/api/db/models/Course.model.js
@@ -0,0 +1,32 @@
+const Sequelize = require("sequelize");
+
+module.exports = {
+ name: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false
+ },
+ shortName: {
+ type: Sequelize.DataTypes.STRING(70),
+ allowNull: false
+ },
+ category: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false
+ },
+ description: {
+ type: Sequelize.DataTypes.STRING(1000),
+ allowNull: true
+ },
+ imageUrl: {
+ type: Sequelize.DataTypes.STRING,
+ allowNull: true
+ },
+ createdById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ lastModifiedById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+};
\ No newline at end of file
diff --git a/api/db/models/CourseEvent.js b/api/db/models/CourseEvent.js
new file mode 100644
index 0000000..36ec2c6
--- /dev/null
+++ b/api/db/models/CourseEvent.js
@@ -0,0 +1,25 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+ notes: {
+ type: Sequelize.DataTypes.STRING(1000),
+ allowNull: true,
+ },
+ runDate: {
+ type: Sequelize.DataTypes.DATEONLY,
+ allowNull: false,
+ defaultValue: Sequelize.DataTypes.NOW
+ },
+ courseTaughtId: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ createdById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ lastModifiedById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+}
\ No newline at end of file
diff --git a/api/db/models/Member.model.js b/api/db/models/Member.model.js
new file mode 100644
index 0000000..14da3c1
--- /dev/null
+++ b/api/db/models/Member.model.js
@@ -0,0 +1,53 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+ name: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false,
+ unique: true
+ },
+ email: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: true,
+ // validate: {
+ // isEmail: true
+ // }
+ },
+ password: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: true
+ },
+ website: {
+ type: Sequelize.DataTypes.STRING(240),
+ allowNull: true,
+ // validate: {
+ // isUrl: true
+ // }
+ },
+ steamId64: {
+ type: Sequelize.DataTypes.STRING(17),
+ allowNull: true,
+ unique: true
+ },
+ steamProfileName: {
+ type: Sequelize.DataTypes.STRING(32),
+ allowNull: true
+ },
+ discordId: {
+ type: Sequelize.DataTypes.STRING(18),
+ unique: true,
+ allowNull: true
+ },
+ discordUsername: {
+ type: Sequelize.DataTypes.STRING(32),
+ allowNull: true
+ },
+ createdById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ lastModifiedById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+};
\ No newline at end of file
diff --git a/api/db/models/MemberStatus.model.js b/api/db/models/MemberStatus.model.js
new file mode 100644
index 0000000..d4b350c
--- /dev/null
+++ b/api/db/models/MemberStatus.model.js
@@ -0,0 +1,16 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+ name: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false,
+ },
+ createdById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ lastModifiedById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+};
\ No newline at end of file
diff --git a/api/db/models/Mission.model.js b/api/db/models/Mission.model.js
new file mode 100644
index 0000000..e69de29
diff --git a/api/db/models/Rank.model.js b/api/db/models/Rank.model.js
new file mode 100644
index 0000000..2238a9f
--- /dev/null
+++ b/api/db/models/Rank.model.js
@@ -0,0 +1,35 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+ name: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false,
+ unique: true
+ },
+ shortName: {
+ type: Sequelize.DataTypes.STRING(70),
+ allowNull: false
+ },
+ category: {
+ type: Sequelize.DataTypes.STRING(100),
+ allowNull: false
+ },
+ sortId: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ defaultValue: 0
+ },
+ imageUrl: {
+ type: Sequelize.DataTypes.STRING(240),
+ allowNull: true,
+ isUrl: true
+ },
+ createdById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+ lastModifiedById: {
+ type: Sequelize.DataTypes.INTEGER,
+ allowNull: false,
+ },
+};
\ No newline at end of file
diff --git a/api/db/routes/Award.route.js b/api/db/routes/Award.route.js
new file mode 100644
index 0000000..32b7717
--- /dev/null
+++ b/api/db/routes/Award.route.js
@@ -0,0 +1,98 @@
+
+const award = require("../controllers/Award.controller.js");
+
+const db = require("..");
+
+var router = require("express").Router();
+
+// Create a new Award
+router.post("/", award.create);
+
+// GET AWARD
+router.get("/", async (req, res) => {
+ const id = req.query.id;
+ if (!id) {
+ return db.Award.findAll()
+ .then(results => res.send(results))
+ }
+
+ return db.Award.findByPk(id)
+ .then(async (award) => {
+ if (award === null) {
+ res.status(404).send({
+ message: `Award with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(award)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving awards."
+ })
+ })
+});
+
+// GET AWARD DETAILS
+router.get("/details", async (req, res) => {
+ const id = req.query.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Award id cannot be empty!"
+ });
+ return
+ }
+ return db.Award.findByPk(id, {
+ include: [
+ 'awardHolders',
+ 'coursesRequired'
+ ]
+ })
+ .then(async (award) => {
+ if (award === null) {
+ res.status(404).send({
+ message: `Award with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(award)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving awards."
+ })
+ })
+});
+
+
+// GET CATEGORIES
+router.get("/categories", async (req, res) => {
+ return db.Award.findAll({
+ attributes: ['category'],
+ group: ['category']
+ })
+ .then(async (awardCategories) => {
+ if (awardCategories === null) {
+ res.status(404).send({
+ message: `Award categories were not found!`
+ });
+ return
+ }
+ res.send(awardCategories.map(awardCategory => awardCategory.category))
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving award categories."
+ })
+ })
+});
+
+
+
+module.exports = {
+ apiPath: "/api/awards",
+ apiRouter: router
+};
\ No newline at end of file
diff --git a/api/db/routes/Course.route.js b/api/db/routes/Course.route.js
new file mode 100644
index 0000000..48b4d68
--- /dev/null
+++ b/api/db/routes/Course.route.js
@@ -0,0 +1,96 @@
+const courses = require("../controllers/Course.controller.js");
+
+const db = require("..");
+
+var router = require("express").Router();
+
+// Create a new Course
+router.post("/", courses.create);
+
+// GET AWARD
+router.get("/", async (req, res) => {
+ const id = req.query.id;
+ if (!id) {
+ return db.Course.findAll()
+ .then(results => res.send(results))
+ }
+
+ return db.Course.findByPk(id)
+ .then(async (course) => {
+ if (course === null) {
+ res.status(404).send({
+ message: `Course with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(course)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving courses."
+ })
+ })
+});
+
+// GET AWARD DETAILS
+router.get("/details", async (req, res) => {
+ const id = req.query.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Course id cannot be empty!"
+ });
+ return
+ }
+ return db.Course.findByPk(id, {
+ include: [
+ 'trainingsHeld',
+ 'possibleAwards',
+ 'sme'
+ ]
+ })
+ .then(async (course) => {
+ if (course === null) {
+ res.status(404).send({
+ message: `Course with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(course)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving courses."
+ })
+ })
+});
+
+
+// GET CATEGORIES
+router.get("/categories", async (req, res) => {
+ return db.Course.findAll({
+ attributes: ['category'],
+ group: ['category']
+ })
+ .then(async (courseCategories) => {
+ if (courseCategories === null) {
+ res.status(404).send({
+ message: `Course categories were not found!`
+ });
+ return
+ }
+ res.send(courseCategories.map(courseCategory => courseCategory.category))
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving course categories."
+ })
+ })
+});
+
+module.exports = {
+ apiPath: "/api/courses",
+ apiRouter: router
+};
\ No newline at end of file
diff --git a/api/db/routes/Member.route.js b/api/db/routes/Member.route.js
new file mode 100644
index 0000000..d14f3ce
--- /dev/null
+++ b/api/db/routes/Member.route.js
@@ -0,0 +1,231 @@
+
+const member = require("../controllers/Member.controller.js");
+
+const db = require("..");
+
+var router = require("express").Router();
+
+// Create a new Member
+router.post("/", member.create);
+// Retrieve all Members
+router.get("/", member.findAll);
+
+// GET MEMBER
+router.get("/:id", async (req, res) => {
+ const id = req.params.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Member id cannot be empty!"
+ });
+ return
+ }
+ return db.Member.findByPk(id)
+ .then(async (member) => {
+ if (member === null) {
+ res.status(404).send({
+ message: `Member with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(member)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members."
+ })
+ })
+});
+
+// GET MEMBER DETAILS
+router.get("/:id/details", async (req, res) => {
+ const id = req.params.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Member id cannot be empty!"
+ });
+ return
+ }
+ return db.Member.findByPk(id, {
+ include: [
+ 'rank',
+ 'status',
+ 'awards',
+ 'coursesSMEFor',
+ 'coursesTaught',
+ 'coursesAttended'
+ ]
+ })
+ .then(async (member) => {
+ if (member === null) {
+ res.status(404).send({
+ message: `Member with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(member)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members."
+ })
+ })
+});
+
+
+// COURSES TAUGHT
+router.get("/:id/courses/taught", async (req, res) => {
+ const id = req.params.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Member id cannot be empty!"
+ });
+ return
+ }
+ return db.Member.findByPk(id)
+ .then(async (member) => {
+ if (member === null) {
+ res.status(404).send({
+ message: `Member with id=${id} was not found!`
+ });
+ return
+ }
+ const courses = await member.getCoursesSMEFor()
+ res.send(courses)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members."
+ })
+ })
+});
+
+// COURSES ATTENDED
+router.get("/:id/courses/attended", async (req, res) => {
+ const id = req.params.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Member id cannot be empty!"
+ });
+ return
+ }
+ return db.Member.findByPk(id)
+ .then(async (member) => {
+ if (member === null) {
+ res.status(404).send({
+ message: `Member with id=${id} was not found!`
+ });
+ return
+ }
+ const courses = await member.getCoursesAttended()
+ res.send(courses)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members."
+ })
+ })
+});
+
+// COURSES SME FOR
+router.get("/:id/courses/sme", async (req, res) => {
+ const id = req.params.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Member id cannot be empty!"
+ });
+ return
+ }
+ return db.Member.findByPk(id)
+ .then(async (member) => {
+ if (member === null) {
+ res.status(404).send({
+ message: `Member with id=${id} was not found!`
+ });
+ return
+ }
+ const courses = await member.getCoursesSMEFor()
+ res.send(courses)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members."
+ })
+ })
+});
+
+
+// UPDATE MEMBER
+router.put("/:id", async (req, res) => {
+ const id = req.params.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Member id cannot be empty!"
+ });
+ return
+ }
+ return db.Member.findByPk(id)
+ .then(async (member) => {
+ if (member === null) {
+ res.status(404).send({
+ message: `Member with id=${id} was not found!`
+ });
+ return
+ }
+ member.set(req.body)
+ await member.save()
+ res.send(member)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members.",
+ error: err
+ })
+ })
+});
+
+
+// Delete a Member with id
+router.delete("/:id", async (req, res) => {
+ const id = req.params.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Member id cannot be empty!"
+ });
+ return
+ }
+ return db.Member.findByPk(id)
+ .then(async (member) => {
+ if (member === null) {
+ res.status(404).send({
+ message: `Member with id=${id} was not found!`
+ });
+ return
+ }
+ await member.destroy()
+ res.send({
+ deleted: member,
+ message: `Member with id=${id} was deleted!`
+ })
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving members.",
+ error: err
+ })
+ })
+});
+
+// Delete all Members
+// router.delete("/", member.deleteAll);
+
+module.exports = {
+ apiPath: "/api/members",
+ apiRouter: router
+};
\ No newline at end of file
diff --git a/api/db/routes/Rank.route.js b/api/db/routes/Rank.route.js
new file mode 100644
index 0000000..3bdd02a
--- /dev/null
+++ b/api/db/routes/Rank.route.js
@@ -0,0 +1,95 @@
+const rank = require("../controllers/Rank.controller.js");
+
+const db = require("..");
+
+var router = require("express").Router();
+
+// Create a new Rank
+router.post("/", rank.create);
+
+// GET RANK
+router.get("/", async (req, res) => {
+ const id = req.query.id;
+ if (!id) {
+ return db.Rank.findAll()
+ .then(results => res.send(results))
+ }
+
+ return db.Rank.findByPk(id)
+ .then(async (rank) => {
+ if (rank === null) {
+ res.status(404).send({
+ message: `Rank with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(rank)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving ranks."
+ })
+ })
+});
+
+// GET RANK DETAILS
+router.get("/details", async (req, res) => {
+ const id = req.query.id;
+ if (!id) {
+ res.status(400).send({
+ message: "Rank id cannot be empty!"
+ });
+ return
+ }
+ return db.Rank.findByPk(id, {
+ include: [
+ 'members',
+ ]
+ })
+ .then(async (rank) => {
+ if (rank === null) {
+ res.status(404).send({
+ message: `Rank with id=${id} was not found!`
+ });
+ return
+ }
+ res.send(rank)
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving ranks."
+ })
+ })
+});
+
+
+// GET CATEGORIES
+router.get("/categories", async (req, res) => {
+ return db.Rank.findAll({
+ attributes: ['category'],
+ group: ['category']
+ })
+ .then(async (rankCategories) => {
+ if (rankCategories === null) {
+ res.status(404).send({
+ message: `Rank categories were not found!`
+ });
+ return
+ }
+ res.send(rankCategories.map(rankCategory => rankCategory.category))
+ })
+ .catch(err => {
+ res.status(500).send({
+ message:
+ err.message || "Some error occurred while retrieving rank categories."
+ })
+ })
+});
+
+
+module.exports = {
+ apiPath: "/api/ranks",
+ apiRouter: router
+};
\ No newline at end of file
diff --git a/api/db/sequelize-docgen.js b/api/db/sequelize-docgen.js
new file mode 100644
index 0000000..586575a
--- /dev/null
+++ b/api/db/sequelize-docgen.js
@@ -0,0 +1,2529 @@
+/**
+ * @apiDefine MemberParam
+ * @apiParam {integer} id
+ * @apiParam {string} name
+ * @apiParam {string} [email]
+ * @apiParam {string} [website]
+ * @apiParam {string} [steamId64]
+ * @apiParam {string} [steamProfileName]
+ * @apiParam {string} [discordId]
+ * @apiParam {string} [discordUsername]
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} [rankId]
+ * @apiParam {integer} [statusId]
+ * @apiParam {Rank} rank
+ * @apiParam {MemberStatus} status
+ * @apiParam {Award[]} awards
+ * @apiParam {Course[]} courseSMEFor
+ * @apiParam {CourseInstance[]} coursesTaught
+ * @apiParam {CourseInstance[]} coursesAttended
+ */
+
+/**
+ * @apiDefine MemberRequest
+ * @apiParamExample {json} Request
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ */
+
+/**
+ * @apiDefine MemberArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine MemberResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ */
+
+/**
+ * @apiDefine MemberArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine AwardParam
+ * @apiParam {integer} id
+ * @apiParam {string} name
+ * @apiParam {string} shortname
+ * @apiParam {string} description
+ * @apiParam {string} [imageUrl]
+ * @apiParam {string} footprint
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} [categoryId]
+ * @apiParam {Member[]} awardHolders
+ * @apiParam {Course[]} coursesRequired
+ * @apiParam {TrainingCategory} trainingCategory
+ */
+
+/**
+ * @apiDefine AwardRequest
+ * @apiParamExample {json} Request
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "coursesRequired": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "possibleAwards": [
+ * {}
+ * ],
+ * "sme": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ */
+
+/**
+ * @apiDefine AwardArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "coursesRequired": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "possibleAwards": [
+ * {}
+ * ],
+ * "sme": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine AwardResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "coursesRequired": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "possibleAwards": [
+ * {}
+ * ],
+ * "sme": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ */
+
+/**
+ * @apiDefine AwardArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "coursesRequired": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "possibleAwards": [
+ * {}
+ * ],
+ * "sme": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CourseParam
+ * @apiParam {integer} id
+ * @apiParam {string} name
+ * @apiParam {string} shortname
+ * @apiParam {string} description
+ * @apiParam {string} [image]
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} [categoryId]
+ * @apiParam {CourseInstance[]} trainingsHeld
+ * @apiParam {Member[]} sme
+ * @apiParam {Award[]} possibleAwards
+ * @apiParam {TrainingCategory} trainingCategory
+ */
+
+/**
+ * @apiDefine CourseRequest
+ * @apiParamExample {json} Request
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ */
+
+/**
+ * @apiDefine CourseArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CourseResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ */
+
+/**
+ * @apiDefine CourseArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine TrainingCategoryParam
+ * @apiParam {integer} id
+ * @apiParam {string} name
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ */
+
+/**
+ * @apiDefine TrainingCategoryRequest
+ * @apiParamExample {json} Request
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ */
+
+/**
+ * @apiDefine TrainingCategoryArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine TrainingCategoryResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ */
+
+/**
+ * @apiDefine TrainingCategoryArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine RankParam
+ * @apiParam {integer} id
+ * @apiParam {string} name
+ * @apiParam {string} category
+ * @apiParam {integer} sortId
+ * @apiParam {string} [imageUrl]
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {Member[]} members
+ */
+
+/**
+ * @apiDefine RankRequest
+ * @apiParamExample {json} Request
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {}
+ * }
+ * ]
+ * }
+ */
+
+/**
+ * @apiDefine RankArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {}
+ * }
+ * ]
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine RankResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {}
+ * }
+ * ]
+ * }
+ */
+
+/**
+ * @apiDefine RankArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {}
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {}
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {}
+ * }
+ * ]
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine MembersAwardsParam
+ * @apiParam {dateonly} actionDate
+ * @apiParam {boolean} awarded
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} awardId
+ * @apiParam {integer} memberId
+ */
+
+/**
+ * @apiDefine MembersAwardsRequest
+ * @apiParamExample {json} Request
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "memberId": 1
+ * }
+ */
+
+/**
+ * @apiDefine MembersAwardsArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "memberId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine MembersAwardsResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "memberId": 1
+ * }
+ */
+
+/**
+ * @apiDefine MembersAwardsArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "memberId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine MembersCoursesParam
+ * @apiParam {dateonly} attendedDate
+ * @apiParam {boolean} passed
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} attendeeId
+ * @apiParam {integer} courseInstanceId
+ */
+
+/**
+ * @apiDefine MembersCoursesRequest
+ * @apiParamExample {json} Request
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "attendeeId": 1,
+ * "courseInstanceId": 1
+ * }
+ */
+
+/**
+ * @apiDefine MembersCoursesArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "attendeeId": 1,
+ * "courseInstanceId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine MembersCoursesResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "attendeeId": 1,
+ * "courseInstanceId": 1
+ * }
+ */
+
+/**
+ * @apiDefine MembersCoursesArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "attendeeId": 1,
+ * "courseInstanceId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CourseInstanceParam
+ * @apiParam {integer} id
+ * @apiParam {dateonly} runDate
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} [courseId]
+ * @apiParam {Course} courseTaught
+ * @apiParam {Member[]} trainers
+ * @apiParam {Member[]} attendees
+ */
+
+/**
+ * @apiDefine CourseInstanceRequest
+ * @apiParamExample {json} Request
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {}
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ */
+
+/**
+ * @apiDefine CourseInstanceArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {}
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CourseInstanceResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {}
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ */
+
+/**
+ * @apiDefine CourseInstanceArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "id": 1,
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "courseId": 1,
+ * "attendees": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "email": "string",
+ * "website": "string",
+ * "steamId64": "string",
+ * "steamProfileName": "string",
+ * "discordId": "string",
+ * "discordUsername": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "rankId": 1,
+ * "statusId": 1,
+ * "coursesAttended": [
+ * {}
+ * ],
+ * "coursesTaught": [
+ * {}
+ * ],
+ * "courseSMEFor": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "image": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "possibleAwards": [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "shortname": "string",
+ * "description": "string",
+ * "imageUrl": "string",
+ * "footprint": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "categoryId": 1,
+ * "trainingCategory": {},
+ * "coursesRequired": [
+ * {}
+ * ],
+ * "awardHolders": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "sme": [
+ * {}
+ * ],
+ * "trainingsHeld": [
+ * {}
+ * ]
+ * }
+ * ],
+ * "awards": [
+ * {}
+ * ],
+ * "status": {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * },
+ * "rank": {
+ * "id": 1,
+ * "name": "string",
+ * "category": "string",
+ * "sortId": 1,
+ * "imageUrl": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "members": [
+ * {}
+ * ]
+ * }
+ * }
+ * ],
+ * "trainers": [
+ * {}
+ * ],
+ * "courseTaught": {}
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine MemberStatusParam
+ * @apiParam {integer} id
+ * @apiParam {string} name
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ */
+
+/**
+ * @apiDefine MemberStatusRequest
+ * @apiParamExample {json} Request
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ */
+
+/**
+ * @apiDefine MemberStatusArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine MemberStatusResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ */
+
+/**
+ * @apiDefine MemberStatusArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "id": 1,
+ * "name": "string",
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123"
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CoursesSMEParam
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} memberId
+ * @apiParam {integer} courseId
+ */
+
+/**
+ * @apiDefine CoursesSMERequest
+ * @apiParamExample {json} Request
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "memberId": 1,
+ * "courseId": 1
+ * }
+ */
+
+/**
+ * @apiDefine CoursesSMEArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "memberId": 1,
+ * "courseId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CoursesSMEResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "memberId": 1,
+ * "courseId": 1
+ * }
+ */
+
+/**
+ * @apiDefine CoursesSMEArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "memberId": 1,
+ * "courseId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CourseInstancesTrainersParam
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} trainerId
+ */
+
+/**
+ * @apiDefine CourseInstancesTrainersRequest
+ * @apiParamExample {json} Request
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "trainerId": 1
+ * }
+ */
+
+/**
+ * @apiDefine CourseInstancesTrainersArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "trainerId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CourseInstancesTrainersResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "trainerId": 1
+ * }
+ */
+
+/**
+ * @apiDefine CourseInstancesTrainersArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "trainerId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CoursesAwardsParam
+ * @apiParam {date} createdAt
+ * @apiParam {date} updatedAt
+ * @apiParam {integer} awardId
+ * @apiParam {integer} courseId
+ */
+
+/**
+ * @apiDefine CoursesAwardsRequest
+ * @apiParamExample {json} Request
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "courseId": 1
+ * }
+ */
+
+/**
+ * @apiDefine CoursesAwardsArrayRequest
+ * @apiParamExample {json} Request
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "courseId": 1
+ * }
+ * ]
+ */
+
+/**
+ * @apiDefine CoursesAwardsResponse
+ * @apiSuccessExample {json} Response
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "courseId": 1
+ * }
+ */
+
+/**
+ * @apiDefine CoursesAwardsArrayResponse
+ * @apiSuccessExample {json} Response
+ * [
+ * {
+ * "createdAt": "2015-12-31T23:59:59.123",
+ * "updatedAt": "2015-12-31T23:59:59.123",
+ * "awardId": 1,
+ * "courseId": 1
+ * }
+ * ]
+ */
+
diff --git a/api/index.js b/api/index.js
new file mode 100644
index 0000000..2ab637e
--- /dev/null
+++ b/api/index.js
@@ -0,0 +1,103 @@
+// set up a basic API for MySQL
+// this is a simple API that will allow us to do basic CRUD operations on our MySQL database
+
+// import the express module
+const express = require('express');
+const cors = require('cors')
+
+// create an express app
+const app = express();
+
+// enable cors
+var corsOptions = {
+ // origin: "http://localhost:5173"
+};
+// app.use(cors(corsOptions));
+app.use(cors());
+
+// parse requests of content-type - application/json
+app.use(express.json());
+
+// parse requests of content-type - application/x-www-form-urlencoded
+app.use(express.urlencoded({ extended: true }));
+
+// include all routes
+const os = require('os');
+const fs = require('fs');
+const path = require('path');
+
+const routesPath = path.join(__dirname, 'db/routes');
+const routeFiles = fs.readdirSync(routesPath);
+
+// auth providers
+const bearerToken = require('express-bearer-token');
+// const { expressjwt: jwt } = require("express-jwt");
+
+for (const file of routeFiles) {
+ const { apiPath, apiRouter } = require(path.join(routesPath, file));
+
+ app.use(
+ apiPath,
+ // JWT Bearer token
+ // jwt({
+ // secret: process.env.JWT_SECRET,
+ // algorithms: ["HS256"],
+ // }),
+ // function (err, req, res, next) {
+ // if (err.name === "UnauthorizedError") {
+ // res.status(401).send("invalid token!");
+ // } else {
+ // next(err);
+ // }
+ // },
+ // simple plaintext Bearer token
+ bearerToken(),
+ function (req, res, next) {
+ if (req.token === process.env.BEARER_TOKEN) {
+ next();
+ } else {
+ res.status(401).send("invalid token!");
+ }
+ },
+ apiRouter
+ );
+}
+
+// sync/init database
+const db = require('./db');
+const dbConfig = require("./db/config.js");
+// if database doesn't exist, create it
+db.instance.query('CREATE DATABASE IF NOT EXISTS ' + dbConfig.DATABASE)
+ .then(() => {
+ console.log('Database created')
+ db.instance.sync({
+ force: true
+ });
+ })
+ .then(() => {
+
+ // configure api docs
+ const swaggerUi = require('swagger-ui-express');
+ const YAML = require('yamljs');
+ const swaggerDocument = YAML.load('openapi.yaml');
+ // local YAML
+ // app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
+ // explorer: true
+ // }));
+
+ // converted from Postman
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, {
+ explorer: true
+ }));
+
+ Promise.resolve()
+ }).then(() => {
+
+ // set port, listen for requests
+ app.listen(3000, function () {
+ console.log('App running on port ' + 3000);
+ });
+
+ }).catch((err) => {
+ console.log(err);
+ });
\ No newline at end of file
diff --git a/api/openapi.json b/api/openapi.json
new file mode 100644
index 0000000..3f96611
--- /dev/null
+++ b/api/openapi.json
@@ -0,0 +1,409 @@
+{
+ "openapi": "3.0.0",
+ "info": {
+ "title": "17th Rangers Database API",
+ "description": "An API for the 17th Rangers Database",
+ "contact": {
+ "email": "indigo@indigofox.dev",
+ "name": "Indigo Fox"
+ },
+ "license": {
+ "name": "MIT",
+ "url": "https://opensource.org/license/mit/"
+ },
+ "version": "0.0.1"
+ },
+ "servers": [
+ {
+ "url": "http://localhost:3001/api",
+ "description": "Development"
+ },
+ {
+ "url": "https://indigofox.dev:9230/api",
+ "description": "Production"
+ }
+ ],
+ "tags": [
+ {
+ "name": "members",
+ "description": "Operations on users/members"
+ },
+ {
+ "name": "ranks",
+ "description": "Rank information & related categories"
+ },
+ {
+ "name": "awards",
+ "description": "Badges & ribbons"
+ },
+ {
+ "name": "courses",
+ "description": "Training courses"
+ },
+ {
+ "name": "courseEvents",
+ "description": "Instances of trainings held for specific courses"
+ },
+ {
+ "name": "member statuses",
+ "description": "Member status indicating active, inactive, company membership, etc."
+ }
+ ],
+ "paths": {
+ "/members": {
+ "get": {
+ "tags": [
+ "members"
+ ],
+ "summary": "Get all members",
+ "description": "Returns a list of all members",
+ "responses": {
+ "200": {
+ "description": "A list of members",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/Member"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "members"
+ ],
+ "summary": "Create a member",
+ "description": "Creates a new member",
+ "requestBody": {
+ "description": "Member object that needs to be added",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MemberPut"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Member created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Member"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid input",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/ClientInputError"
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/members/{memberId}": {
+ "get": {
+ "tags": [
+ "members"
+ ],
+ "summary": "Get a member by ID",
+ "description": "Returns a single member",
+ "parameters": [
+ {
+ "name": "memberId",
+ "in": "path",
+ "description": "ID of member to return",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Member found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Member"
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "put": {
+ "tags": [
+ "members"
+ ],
+ "summary": "Update a member by ID",
+ "description": "Updates a member",
+ "parameters": [
+ {
+ "name": "memberId",
+ "in": "path",
+ "description": "ID of member to update",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int64"
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "Member object that needs to be updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MemberPut"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "Member updated",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Member"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid input",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/ClientInputError"
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "404": {
+ "$ref": "#/components/responses/NotFound"
+ },
+ "500": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/training-reports": {
+ "get": {
+ "tags": [
+ "training-reports"
+ ],
+ "summary": "Get all training reports",
+ "description": "Returns a list of all training reports",
+ "responses": {
+ "200": {
+ "description": "A list of training reports",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/TrainingReport"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "tags": [
+ "training-reports"
+ ],
+ "summary": "Create a training report",
+ "description": "Creates a new training report",
+ "requestBody": {
+ "$ref": "#/components/requestBodies/CourseEventTrainingReport",
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Training report created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/TrainingReport"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Invalid input",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/ClientInputError"
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Unauthorized"
+ },
+ "500": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/responses/InternalServerError"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Member": {
+ "$ref": "schemas/member.json"
+ },
+ "MemberPut": {
+ "$ref": "schemas/memberPut.json"
+ },
+ "Rank": {
+ "$ref": "schemas/rank.json"
+ },
+ "Award": {
+ "$ref": "schemas/award.json"
+ },
+ "AwardAction": {
+ "$ref": "schemas/awardAction.json"
+ },
+ "CourseEvent": {
+ "$ref": "schemas/courseEvent.json"
+ },
+ "Error": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "message": {
+ "description": "A human readable error message",
+ "type": "string"
+ }
+ }
+ }
+ },
+ "requestBodies": {
+ "CourseEventTrainingReport": {
+ "$ref": "requestBodies/courseEventTrainingReport.json"
+ }
+ },
+ "responses": {
+ "Unauthorized": {
+ "description": "Unauthorized"
+ },
+ "ClientInputError": {
+ "description": "Client Input Error"
+ },
+ "NotFound": {
+ "description": "Not Found"
+ },
+ "InternalServerError": {
+ "description": "Internal Server Error"
+ }
+ },
+ "securitySchemes": {
+ "bearerAuth": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "plain-text"
+ }
+ }
+ },
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/api/openapi.yaml b/api/openapi.yaml
new file mode 100644
index 0000000..bda5846
--- /dev/null
+++ b/api/openapi.yaml
@@ -0,0 +1,1565 @@
+openapi: "3.0.3"
+info:
+ title: 17th Rangers Database API
+ description: An API for the 17th Rangers Database
+ contact:
+ email: indigo@indigofox.dev
+ license:
+ name: MIT
+ url: https://opensource.org/license/mit/
+ version: "0.0.1"
+servers:
+ - url: http://localhost:3001/api
+ description: Development
+ - url: https://indigofox.dev:9230/api
+ description: Production
+tags:
+ - name: members
+ description: Operations on users/members
+ - name: ranks
+ description: Rank information & related categories
+ - name: awards
+ description: Badges & ribbons
+ - name: courses
+ description: Training courses
+ - name: member statuses
+ description: Member status indicating active, inactive, company membership, etc.
+paths:
+ /members:
+ get:
+ tags:
+ - members
+ summary: Get all members
+ description: Get all members
+ operationId: getMembers
+ parameters: []
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Member"
+
+ post:
+ tags:
+ - members
+ summary: Add one or more Members
+ description: Add a new Member
+ operationId: addMember
+ requestBody:
+ description: Array of Member objects to add
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Member"
+ example:
+ - name: Ocean
+ email: ocean@example.com
+ website: https://example.com
+ - name: Paradox
+ steamId64: "76561198000000000"
+ email: paradox@example.com
+ - name: Rhy94
+ discordId: "123456789012345678"
+ - name: Randy
+ - name: Pedano
+ - name: French Toast?
+ - name: Sherman
+ - name: Trigger Tigger
+ - name: Giland
+ - name: Doc Halladay
+ - name: EagleTrooper
+ - name: Bones
+ - name: burntcopper
+ - name: Hakugard
+ - name: Hizumi
+ - name: MattPod
+ - name: Newt
+ - name: Raven367
+ - name: Sadert
+ - name: Sly
+ - name: TrashPandaTX
+ - name: Alvil
+ - name: birdman850
+ - name: Goblin
+ - name: Page
+ - name: Slothdotpy
+ - name: Stoner
+ - name: Waykook
+ - name: A-Train
+ - name: Anderp
+ - name: Alecazam2001
+ - name: Blaze
+ - name: BlueFist13F
+ - name: Chops
+ - name: Fantasy
+ - name: FreqiMANN
+ - name: Gio
+ - name: Homie
+ - name: IndigoFox
+ - name: JustSam0709
+ - name: KarlKirbs
+ - name: Kfir
+ - name: Khodi
+ - name: Kron
+ - name: Lulux Liengod
+ - name: MechaSaurusRex
+ - name: naga
+ - name: Pixy
+ - name: PrivateKitty
+ - name: Silent Assassin
+ - name: TheSaladKing
+ - name: tiberius
+ - name: WacktheMedic
+ - name: Buck
+ - name: Cletus
+ - name: Dan
+ - name: evilbawb
+ - name: Flunky
+ - name: Jacket
+ - name: Lewis
+ - name: TheWikiFish
+ - name: Waffle
+ - name: Aiglos
+ - name: Bears
+ - name: Blackwell
+ - name: Chugalug
+ - name: Comra
+ - name: DrippyIce
+ - name: Dr.Machicken
+ - name: Gretalian or G
+ - name: Hepheastus
+ - name: horrorhynde
+ - name: HowIsAsh
+ - name: kuya luya ginger
+ - name: Iron
+ - name: King 0-1
+ - name: Mr_Hghwy
+ - name: SocietalPhoenix
+ - name: Snowbandit1861
+ - name: Skark18
+ - name: Tazer
+ - name: Teal
+ - name: TimmyTheWhale
+ - name: Wimpy
+ - name: VioletSnow
+ - name: Xufffer
+ - name: Ken
+ - name: Meeseks
+ - name: ajdj100
+ - name: Jaeger22
+ - name: Raccoon
+ - name: WallyWorld
+ - name: BadDad
+ - name: Caboose
+ - name: Hairy
+ - name: Muffin
+ - name: McCann
+ - name: Andi
+ - name: Blitzcraig
+ - name: Okami
+ - name: Radd
+ - name: Ryan
+ - name: Adrian
+ - name: Broski
+ - name: Gary
+ - name: gossler
+ - name: Griggs
+ - name: Grizzly(Jay)
+ - name: Kerwin
+ - name: Pancho
+ - name: Mcanaan
+ - name: Midnightowl23
+ - name: "Null"
+ - name: Vlad
+ - name: Thats Colin
+ - name: JesseKjames08
+ - name: Opossum42
+ - name: BannanaBoat
+ - name: Captin228
+ - name: Juice
+ - name: Rosey
+ - name: Sassy
+ - name: Scarab
+ - name: Taters159
+ - name: Zeps
+ - name: Xylemic
+ - name: Maestroshake
+ - name: Ramsay
+ - name: PapkaMush
+ - name: Mclovin
+ - name: 757Live
+ - name: Talon
+ - name: OdinPanda
+ - name: Zombie
+ - name: Puma
+ - name: Rugged
+ - name: Woods
+ - name: Jackson
+ - name: Sawgunner17
+ - name: sean97T
+ - name: TurtleBread
+ - name: Marchand
+ - name: Jordon
+ - name: codux
+ - name: WNUSS97
+ - name: Rose
+ - name: Malk
+ responses:
+ "201":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Member"
+ # empty array
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+ example: []
+
+ "207":
+ description: Some members were created successfully.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Member"
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+
+ "400":
+ description: Invalid input
+
+ "500":
+ description: Internal server error
+ content:
+ application/json:
+ schema:
+ # allOf:
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: Failed to create any Members.
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Member"
+ example: []
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+
+ /members/{id}:
+ get:
+ tags:
+ - members
+ summary: Get a member by id
+ description: Get a specific member by providing an id.
+ operationId: getMemberById
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to return
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Member"
+
+ "404":
+ description: Member not found
+
+ put:
+ tags:
+ - members
+ summary: Update a member by id
+ description: Update core member details by providing an id.
+ operationId: updateMemberById
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to update
+ required: true
+ schema:
+ type: integer
+ format: int64
+ requestBody:
+ description: Member object that needs to be updated
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Member"
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Member"
+ "400":
+ description: Invalid input
+ "404":
+ description: Member not found
+
+ delete:
+ tags:
+ - members
+ summary: Delete a member by id
+ description: Delete a member by providing an id.
+ operationId: deleteMemberById
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to delete
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ allOf:
+ - type: object
+ properties:
+ message:
+ type: string
+ example: "Member with id=${id} was deleted!"
+ deletedMember:
+ $ref: "#/components/schemas/Member"
+
+ "400":
+ description: Invalid input
+ "404":
+ description: Member not found
+
+ /members/{id}/details:
+ get:
+ tags:
+ - members
+ summary: Get a member's details by id
+ description: Get member details by providing an id. Returns additional information like courses they've attended, taught, or are subject matter experts for, as well as awards they've earned.
+ operationId: getMemberDetailsById
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to return
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/MemberExtended"
+ "404":
+ description: Member not found
+ /members/{id}/courses/taught:
+ get:
+ tags:
+ - members
+ summary: Get course instances taught by a member
+ description: Get course instances taught by a member
+ operationId: getCourseInstancesTaughtByMember
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to return
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/CourseEvent"
+ /members/{id}/courses/attended:
+ get:
+ tags:
+ - members
+ summary: Get course instances attended by a member
+ description: Get course instances by a member
+ operationId: getCourseInstancesAttendedByMember
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to return
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/CourseEvent"
+ /members/{id}/courses/sme:
+ get:
+ tags:
+ - members
+ summary: Get courses a member is a subject matter expert for
+ description: Get courses a member is a subject matter expert for
+ operationId: getCoursesSMEByMember
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to return
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Course"
+
+ /members/{id}/awards:
+ get:
+ tags:
+ - members
+ summary: Get awards earned by a member
+ description: Get awards earned by a member
+ operationId: getAwardsEarnedByMember
+ parameters:
+ - name: id
+ in: path
+ description: ID of member to return
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Award"
+
+ /awards:
+ get:
+ tags:
+ - awards
+ summary: Get all awards
+ description: Get all awards
+ operationId: getAwards
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Award"
+ "500":
+ description: Internal server error
+
+ post:
+ tags:
+ - awards
+ summary: Create awards
+ description: Create awards
+ operationId: createAwards
+ requestBody:
+ description: Award objects that need to be created
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Award"
+ example:
+ - name: "Basic Training"
+ shortname: "BT"
+ description: "Marks the completion of basic training and formal induction to the community."
+ imageUrl: "https://img.guildedcdn.com/ContentMedia/831b507af3f11728cfe6f0b2ac95e9cf-Full.webp?w=100&h=30"
+ footprint: "ribbon"
+ category: "Infantry"
+ - name: "Advanced Infantry Training"
+ shortname: "AIT"
+ description: "Awarded upon successful completion of all 4 parts of AIT."
+ imageUrl: "https://img.guildedcdn.com/ContentMedia/d6c823b6c23a4bc4a9dfc73ae98d6e27-Full.webp?w=100&h=30"
+ footprint: "ribbon"
+ category: "Infantry"
+
+ responses:
+ "201":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Award"
+ # empty array
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+ example: []
+
+ "207":
+ description: Some ranks were created successfully.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Award"
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+
+ "400":
+ description: Invalid input
+
+ "500":
+ description: Internal server error
+ content:
+ application/json:
+ schema:
+ # allOf:
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: Failed to create any Awards.
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Award"
+ example: []
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+ /awards?id={id}:
+ get:
+ tags:
+ - awards
+ summary: Get award by id
+ description: Get award by id
+ operationId: getAwardById
+ parameters:
+ - name: id
+ in: path
+ description: Award id
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Award"
+ "400":
+ description: Invalid ID supplied
+ "404":
+ description: Award not found
+ "500":
+ description: Internal server error
+ /awards/details?id={id}:
+ get:
+ tags:
+ - awards
+ summary: Get award details by id
+ description: Retrieves information about an award, including members who hold it and courses required to achieve it.
+ operationId: getAwardDetailsById
+ parameters:
+ - name: id
+ in: path
+ description: Award id
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AwardDetail"
+ "400":
+ description: Invalid ID supplied
+ "404":
+ description: Award not found
+ "500":
+ description: Internal server error
+ /awards/categories:
+ get:
+ tags:
+ - awards
+ summary: Get all award categories
+ description: Get all award categories
+ operationId: getAwardCategories
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ example: [Infantry, Support, Specialty, Tenure, Commendation]
+ "404":
+ description: Award categories not found
+ "500":
+ description: Internal server error
+
+ /ranks:
+ get:
+ tags:
+ - ranks
+ summary: Get all ranks
+ description: Get all ranks
+ operationId: getRanks
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Rank"
+ "500":
+ description: Internal server error
+
+ post:
+ tags:
+ - ranks
+ summary: Create ranks
+ description: Create ranks
+ operationId: createRanks
+ requestBody:
+ description: Rank objects that need to be created
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Rank"
+ example:
+ - name: Recruit
+ shortname: RCT
+ category: Enlisted
+ sort_id: 22
+ image_url: https://i.imgur.com/UE1Zs6g.png
+ - name: Private
+ shortname: PVT
+ category: Enlisted
+ sort_id: 21
+ image_url: http://i.imgur.com/Wh4nYns.png
+ - name: Private First Class
+ shortname: PFC
+ category: Enlisted
+ sort_id: 20
+ image_url: http://i.imgur.com/9V9PBDi.png
+ - name: Specialist
+ shortname: SPC
+ category: Enlisted
+ sort_id: 19
+ image_url: http://i.imgur.com/jEEuKKB.png
+ - name: Corporal
+ shortname: CPL
+ category: NCO
+ sort_id: 18
+ image_url: http://i.imgur.com/nfZrieG.png
+ - name: Sergeant
+ shortname: SGT
+ category: NCO
+ sort_id: 17
+ image_url: http://i.imgur.com/hfGy0ZZ.png
+ - name: Staff Sergeant
+ shortname: SSG
+ category: NCO
+ sort_id: 16
+ image_url: http://i.imgur.com/ZVg95ep.png
+ - name: Sergeant 1st Class
+ shortname: SFC
+ category: NCO
+ sort_id: 15
+ image_url: ""
+ - name: Master Sergeant
+ shortname: MSG
+ category: NCO
+ sort_id: 14
+ image_url: ""
+ - name: 1st Sergeant
+ shortname: 1SG
+ category: NCO
+ sort_id: 13
+ image_url: ""
+ - name: Sergeant Major
+ shortname: SGM
+ category: NCO
+ sort_id: 12
+ image_url: ""
+ - name: Warrant Officer 1
+ shortname: W01
+ category: Enlisted
+ sort_id: 11
+ image_url: ""
+ - name: Chief Warrant Officer 2
+ shortname: CW02
+ category: Enlisted
+ sort_id: 10
+ image_url: ""
+ - name: Chief Warrant Officer 3
+ shortname: CW03
+ category: NCO
+ sort_id: 9
+ image_url: ""
+ - name: Chief Warrant Officer 4
+ shortname: CW04
+ category: NCO
+ sort_id: 8
+ image_url: ""
+ - name: Chief Warrant Officer 5
+ shortname: CW05
+ category: NCO
+ sort_id: 7
+ image_url: ""
+ - name: 2nd Lieutenant
+ shortname: 2LT
+ category: Officer
+ sort_id: 6
+ image_url: ""
+ - name: 1st Lieutenant
+ shortname: 1LT
+ category: Officer
+ sort_id: 5
+ image_url: ""
+ - name: Captain
+ shortname: CPT
+ category: Officer
+ sort_id: 4
+ image_url: ""
+ - name: Major
+ shortname: MAJ
+ category: Officer
+ sort_id: 3
+ image_url: ""
+ - name: Lieutenant Colonel
+ shortname: LTC
+ category: Officer
+ sort_id: 2
+ image_url: ""
+ - name: Staff
+ shortname: STAFF
+ category: ""
+ sort_id: 1
+ image_url: ""
+
+ responses:
+ "201":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Rank"
+ # empty array
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+ example: []
+
+ "207":
+ description: Some ranks were created successfully.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Rank"
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+
+ "400":
+ description: Invalid input
+
+ "500":
+ description: Internal server error
+ content:
+ application/json:
+ schema:
+ # allOf:
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: Failed to create any Ranks.
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Rank"
+ example: []
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+ /ranks?id={id}:
+ get:
+ tags:
+ - ranks
+ summary: Get rank by id
+ description: Get rank by id
+ operationId: getRankById
+ parameters:
+ - name: id
+ in: path
+ description: Rank id
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Rank"
+ "400":
+ description: Invalid ID supplied
+ "404":
+ description: Rank not found
+ "500":
+ description: Internal server error
+ /ranks/details?id={id}:
+ get:
+ tags:
+ - ranks
+ summary: Get rank details by id
+ description: Retrieves information about an rank, including members who hold it and courses required to achieve it.
+ operationId: getRankDetailsById
+ parameters:
+ - name: id
+ in: path
+ description: Rank id
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/RankDetail"
+ "400":
+ description: Invalid ID supplied
+ "404":
+ description: Rank not found
+ "500":
+ description: Internal server error
+ /ranks/categories:
+ get:
+ tags:
+ - ranks
+ summary: Get all rank categories
+ description: Get all rank categories
+ operationId: getRankCategories
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ example: [Enlisted, NCO, Officer, Staff]
+ "404":
+ description: Rank categories not found
+ "500":
+ description: Internal server error
+
+ /courses:
+ get:
+ tags:
+ - courses
+ summary: Get all courses
+ description: Get all courses
+ operationId: getCourses
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Course"
+ "500":
+ description: Internal server error
+
+ post:
+ tags:
+ - courses
+ summary: Create courses
+ description: Create courses
+ operationId: createCourses
+ requestBody:
+ description: Course objects that need to be created
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Course"
+ example:
+ - name: 68 Whiskey (Medical)
+ shortname: 68Whiskey
+ description: 68 Whiskey is a specialized, advanced infantry role that is solely focused on the sustainability and survivability of personnel after engagements. 68W are uniquely equipped to provide definitive care to all friendly personnel and fulfill a vital backline role in the unit. By the end of this training, trainees will be able to identify, treat, and clear any event from routine patient care to mass casualty incidents.
+ category: Infantry
+ - name: Heavy Weapons Training
+ shortname: Heavy Weapons
+ description: Requirement 1 of 2 for the Heavy Weapons Ribbon
+ category: Infantry
+ - name: Combat Life Saver (CLS)
+ shortname: CLS
+ description: The Combat Life Saver is an important and unique role within the 17th. It is one of the few roles that allow you to serve a dual-purpose role. You are a rifleman (first and foremost), however, during certain instances, your role changes and you become a first responder and a vitally important assistant to the 68W trained Squad Medic. How well you do your job can be the difference between a mission success and a fireteam or even squad-level wipe. A well-trained and efficient Combat Life Saver can be just as effective in saving teammates as a 68W trained Squad Medic. This training is designed to teach all the core skills and knowledge required to effectively fill the role of Combat Life Saver.
+ category: Infantry
+ - name: Advanced Infantry Training 1
+ shortname: AIT1
+ description: Standard Field Operations
+ category: Infantry
+ - name: Advanced Infantry Training 2
+ shortname: AIT2
+ description: Standard Combat Operations
+ category: Infantry
+ - name: Advanced Infantry Training 3
+ shortname: AIT3
+ description: Urban Combat Operations
+ category: Infantry
+ - name: Advanced Infantry Training 4
+ shortname: AIT4
+ description: CQB Training
+ category: Infantry
+ responses:
+ "201":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Course"
+ # empty array
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+ example: []
+
+ "207":
+ description: Some ranks were created successfully.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Course"
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+
+ "400":
+ description: Invalid input
+
+ "500":
+ description: Internal server error
+ content:
+ application/json:
+ schema:
+ # allOf:
+ type: object
+ properties:
+ message:
+ type: string
+ description: The error message.
+ example: Failed to create any Courses.
+ successes:
+ type: array
+ items:
+ $ref: "#/components/schemas/Course"
+ example: []
+ failures:
+ type: array
+ items:
+ $ref: "#/components/schemas/SequelizeError"
+ /courses?id={id}:
+ get:
+ tags:
+ - courses
+ summary: Get course by id
+ description: Get course by id
+ operationId: getCourseById
+ parameters:
+ - name: id
+ in: path
+ description: Course id
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Course"
+ "400":
+ description: Invalid ID supplied
+ "404":
+ description: Course not found
+ "500":
+ description: Internal server error
+ /courses/details?id={id}:
+ get:
+ tags:
+ - courses
+ summary: Get course details by id
+ description: Retrieves information about an course, including members who hold it and courses required to achieve it.
+ operationId: getCourseDetailsById
+ parameters:
+ - name: id
+ in: path
+ description: Course id
+ required: true
+ schema:
+ type: integer
+ format: int64
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CourseDetail"
+ "400":
+ description: Invalid ID supplied
+ "404":
+ description: Course not found
+ "500":
+ description: Internal server error
+ /courses/categories:
+ get:
+ tags:
+ - courses
+ summary: Get all course categories
+ description: Get all course categories
+ operationId: getCourseCategories
+ responses:
+ "200":
+ description: successful operation
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ example: [Infantry, Support, Specialty, Tenure, Commendation]
+ "404":
+ description: Course categories not found
+ "500":
+ description: Internal server error
+components:
+ schemas:
+ Member:
+ type: object
+ properties:
+ id:
+ type: integer
+ format: int64
+ readOnly: true
+ name:
+ type: string
+ maxLength: 100
+ email:
+ type: string
+ maxLength: 100
+ nullable: true
+ website:
+ type: string
+ maxLength: 240
+ nullable: true
+ steamId64:
+ type: string
+ maxLength: 17
+ nullable: true
+ steamProfileName:
+ type: string
+ maxLength: 32
+ nullable: true
+ discordId:
+ type: string
+ maxLength: 18
+ nullable: true
+ discordUsername:
+ type: string
+ maxLength: 32
+ nullable: true
+ createdAt:
+ type: string
+ format: date-time
+ readOnly: true
+ updatedAt:
+ type: string
+ format: date-time
+ readOnly: true
+ rank:
+ $ref: "#/components/schemas/Rank"
+ status:
+ $ref: "#/components/schemas/MemberStatus"
+ example:
+ id: 1
+ name: John Doe
+ email: test@example.com
+ website: https://example.com
+ steamId64: "76561198000000000"
+ steamProfileName: JohnDoe
+ discordId: "123456789012345678"
+ discordUsername: JohnDoe#1234
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+ MemberExtended:
+ allOf:
+ - $ref: "#/components/schemas/Member"
+ - type: object
+ properties:
+ awards:
+ type: array
+ items:
+ $ref: "#/components/schemas/Award"
+ coursesSME:
+ type: array
+ items:
+ $ref: "#/components/schemas/Course"
+ coursesTaught:
+ type: array
+ items:
+ $ref: "#/components/schemas/CourseEvent"
+ coursesAttended:
+ type: array
+ items:
+ $ref: "#/components/schemas/CourseEvent"
+ example:
+ id: 1
+ name: John Doe
+ email: Phantom29@opera.com
+ website: https://example.com
+ steamId64: "76561198000000000"
+ steamProfileName: JohnDoe
+ discordId: "123456789012345678"
+ discordUsername: JohnDoe#1234
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+ awards:
+ - id: 3
+ name: "Basic Rifle Marksmanship"
+ shortname: "BRM"
+ description: "This course is earned by completing the Basic Rifle Marksmanship course."
+ category: Infantry
+ imageUrl: "https://example.com/brm.png"
+ footprint: "badge"
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+ coursesSME:
+ - id: 1
+ name: "Basic Rifle Marksmanship"
+ shortname: "BRM"
+ description: "This course is designed to teach the basics of rifle marksmanship. It is intended for new members and those who have not received formal training in the past."
+ imageUrl: "https://example.com/brm.png"
+ coursesTaught:
+ - id: 24
+ runDate: "2020-01-02"
+ createdAt: "2020-01-02T00:00:00.000Z"
+ updatedAt: "2020-01-02T00:00:00.000Z"
+ coursesAttended:
+ - id: 24
+ runDate: "2020-01-02"
+ createdAt: "2020-01-02T00:00:00.000Z"
+ updatedAt: "2020-01-02T00:00:00.000Z"
+ Rank:
+ type: object
+ description: Describes the Rank a Member holds, including a category and custom sort id.
+ properties:
+ id:
+ type: integer
+ format: int64
+ readOnly: true
+ name:
+ type: string
+ maxLength: 100
+ category:
+ type: string
+ maxLength: 100
+ enum:
+ - Enlisted
+ - Officer
+ - NCO
+ sortId:
+ type: integer
+ default: 0
+ imageUrl:
+ type: string
+ maxLength: 240
+ createdAt:
+ type: string
+ format: date-time
+ readOnly: true
+ updatedAt:
+ type: string
+ format: date-time
+ readOnly: true
+ example:
+ id: 1
+ name: Private First Class
+ category: Enlisted
+ sortId: 2
+ imageUrl: https://example.com/image.png
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+ RankDetail:
+ allOf:
+ - $ref: "#/components/schemas/Rank"
+ - type: object
+ properties:
+ members:
+ type: array
+ items:
+ $ref: "#/components/schemas/Member"
+ example:
+ id: 1
+ name: Private First Class
+ category: Enlisted
+ sortId: 2
+ imageUrl: https://example.com/image.png
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+ members:
+ - id: 1
+ name: John Doe
+ email: test@example.com
+ website: https://example.com
+ steamId64: "76561198000000000"
+ MemberStatus:
+ type: object
+ description: Describes a Member's company membership or inactivity reason.
+ properties:
+ id:
+ type: integer
+ format: int64
+ readOnly: true
+ name:
+ type: string
+ maxLength: 100
+ createdAt:
+ type: string
+ format: date-time
+ readOnly: true
+ updatedAt:
+ type: string
+ format: date-time
+ readOnly: true
+ example:
+ id: 1
+ name: Alpha Company
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+ Award:
+ type: object
+ description: Defines an course, usually a ribbon or badge, that a Member can earn by taking Courses or through other means.
+ properties:
+ id:
+ type: integer
+ format: int64
+ readOnly: true
+ name:
+ type: string
+ maxLength: 100
+ shortname:
+ type: string
+ maxLength: 70
+ description:
+ type: string
+ maxLength: 1000
+ nullable: true
+ imageUrl:
+ type: string
+ maxLength: 255
+ nullable: true
+ description: An image representing the course.
+ footprint:
+ type: string
+ description: Whether the course is a ribbon or a badge.
+ maxLength: 45
+ enum:
+ - "Ribbon"
+ - "Badge"
+ createdAt:
+ type: string
+ format: date-time
+ readOnly: true
+ updatedAt:
+ type: string
+ format: date-time
+ readOnly: true
+ example:
+ id: 3
+ name: "Basic Rifle Marksmanship"
+ shortname: "BRM"
+ description: "This course is earned by completing the Basic Rifle Marksmanship course."
+ imageUrl: "https://example.com/brm.png"
+ footprint: "badge"
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+
+ AwardDetail:
+ allOf:
+ - $ref: "#/components/schemas/Award"
+ - type: object
+ properties:
+ awardHolders:
+ type: array
+ items:
+ $ref: "#/components/schemas/Member"
+ coursesRequired:
+ type: array
+ items:
+ $ref: "#/components/schemas/Course"
+ example:
+ id: 3
+ name: "Basic Rifle Marksmanship"
+ shortname: "BRM"
+ description: "This badge is earned by completing the Basic Rifle Marksmanship course."
+ category: "Infantry"
+ imageUrl: "https://example.com/brm.png"
+ footprint: "Badge"
+ createdAt: 2020-01-01 00:00:00.000Z
+ updatedAt: 2020-01-01 00:00:00.000Z
+ awardHolders:
+ - id: 1
+ name: John Doe
+ email: test@example.com
+ website: https://example.com
+ steamId64: 76561198000000000
+ coursesRequired:
+ - id: 1
+ name: "Basic Rifle Marksmanship"
+ shortname: "BRM"
+ description: "This course is designed to teach the basics of rifle marksmanship. It is intended for new members and those who have not received formal training in the past."
+ category: "Infantry"
+ imageUrl: "https://example.com/brm.png"
+ Course:
+ type: object
+ description: Represents the definition of training course. It may contribute toward earning an Award.
+ properties:
+ id:
+ type: integer
+ format: int64
+ readOnly: true
+ name:
+ type: string
+ maxLength: 100
+ shortname:
+ type: string
+ maxLength: 70
+ description:
+ type: string
+ maxLength: 1000
+ nullable: true
+ imageUrl:
+ type: string
+ description: An image representing the course.
+ maxLength: 255
+ nullable: true
+ createdAt:
+ type: string
+ format: date-time
+ readOnly: true
+ updatedAt:
+ type: string
+ format: date-time
+ readOnly: true
+ example:
+ id: 1
+ name: "Basic Rifle Marksmanship"
+ shortname: "BRM"
+ description: "This course is designed to teach the basics of rifle marksmanship. It is intended for new members and those who have not received formal training in the past."
+ imageUrl: "https://example.com/brm.png"
+
+ CourseDetail:
+ allOf:
+ - $ref: "#/components/schemas/Course"
+ - type: object
+ properties:
+ sme:
+ type: array
+ items:
+ $ref: "#/components/schemas/Member"
+ trainingsHeld:
+ type: array
+ items:
+ $ref: "#/components/schemas/CourseEvent"
+ possibleAwards:
+ type: array
+ items:
+ $ref: "#/components/schemas/Award"
+ example:
+ id: 1
+ name: "Basic Rifle Marksmanship"
+ shortname: "BRM"
+ description: "This course is designed to teach the basics of rifle marksmanship. It is intended for new members and those who have not received formal training in the past."
+ imageUrl: "https://example.com/brm.png"
+ sme:
+ - id: 1
+ name: John Doe
+ email: test@example.com
+ website: https://example.com
+ steamId64: 76561198000000000
+
+ CourseEvent:
+ type: object
+ description: Represents a specific instance of a Course, including the date it was taught, who taught it, and who attended.
+ properties:
+ id:
+ type: integer
+ format: int64
+ readOnly: true
+ runDate:
+ type: string
+ format: date
+ createdAt:
+ type: string
+ format: date-time
+ readOnly: true
+ updatedAt:
+ type: string
+ format: date-time
+ readOnly: true
+ example:
+ id: 24
+ runDate: "2020-01-02"
+ createdAt: "2020-01-02T00:00:00.000Z"
+ updatedAt: "2020-01-02T00:00:00.000Z"
+ SequelizeError:
+ type: object
+ description: A Sequelize error.
+ properties:
+ message:
+ type: string
+ type:
+ type: string
+ path:
+ type: string
+ value:
+ type: string
+ origin:
+ type: string
+ instance:
+ type: object
+ validatorKey:
+ type: string
+ validatorName:
+ type: string
+ validatorArgs:
+ type: array
+ items:
+ type: string
+
+ responses:
+ UnauthorizedError:
+ description: Access token is missing or invalid
+
+ # 1) Define the security scheme type (HTTP bearer)
+ securitySchemes:
+ bearerAuth: # arbitrary name for the security scheme
+ type: http
+ scheme: bearer
+ bearerFormat: JWT # optional, arbitrary value for documentation purposes
+# 2) Apply the security globally to all operations
+security:
+ - bearerAuth: [] # use the same name as above
diff --git a/api/package-lock.json b/api/package-lock.json
new file mode 100644
index 0000000..1f001c1
--- /dev/null
+++ b/api/package-lock.json
@@ -0,0 +1,1564 @@
+{
+ "name": "api",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "api",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.3.4",
+ "cors": "^2.8.5",
+ "dotenv": "^16.0.3",
+ "express": "^4.18.2",
+ "express-bearer-token": "^2.4.0",
+ "express-jwt": "^8.4.1",
+ "mysql2": "^3.2.0",
+ "nodemon": "^2.0.22",
+ "sequelize": "^6.29.3",
+ "swagger-ui-express": "^4.6.2",
+ "yamljs": "^0.3.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
+ "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz",
+ "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
+ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.15.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz",
+ "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw=="
+ },
+ "node_modules/@types/validator": {
+ "version": "13.7.14",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.14.tgz",
+ "integrity": "sha512-J6OAed6rhN6zyqL9Of6ZMamhlsOEU/poBVvbHr/dKOYKTeuYYMlDkMv+b6UUV0o2i0tw73cgyv/97WTWaUl0/g=="
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
+ "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+ "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.1",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
+ "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
+ "dependencies": {
+ "cookie": "0.4.1",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
+ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+ "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dottie": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.3.tgz",
+ "integrity": "sha512-4liA0PuRkZWQFQjwBypdxPfZaRWiv5tkhMXY2hzsa2pNf5s7U3m9cwUchfNKe8wZQxdGPQQzO6Rm2uGe0rvohQ=="
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.18.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+ "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.1",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.5.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.2.0",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.11.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/express-bearer-token": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/express-bearer-token/-/express-bearer-token-2.4.0.tgz",
+ "integrity": "sha512-2+kRZT2xo+pmmvSY7Ma5FzxTJpO3kGaPCEXPbAm3GaoZ/z6FE4K6L7cvs1AUZwY2xkk15PcQw7t4dWjsl5rdJw==",
+ "dependencies": {
+ "cookie": "^0.3.1",
+ "cookie-parser": "^1.4.4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/express-bearer-token/node_modules/cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express-jwt": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.4.1.tgz",
+ "integrity": "sha512-IZoZiDv2yZJAb3QrbaSATVtTCYT11OcqgFGoTN4iKVyN6NBkBkhtVIixww5fmakF0Upt5HfOxJuS6ZmJVeOtTQ==",
+ "dependencies": {
+ "@types/jsonwebtoken": "^9",
+ "express-unless": "^2.1.3",
+ "jsonwebtoken": "^9.0.0"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/express-unless": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz",
+ "integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ=="
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
+ },
+ "node_modules/inflection": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz",
+ "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==",
+ "engines": [
+ "node >= 0.4.0"
+ ]
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+ "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash": "^4.17.21",
+ "ms": "^2.1.1",
+ "semver": "^7.3.8"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/long": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz",
+ "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A=="
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.41",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz",
+ "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==",
+ "dependencies": {
+ "moment": "^2.29.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/mysql2": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.2.0.tgz",
+ "integrity": "sha512-0Vn6a9WSrq6fWwvPgrvIwnOCldiEcgbzapVRDAtDZ4cMTxN7pnGqCTx8EG32S/NYXl6AXkdO+9hV1tSIi/LigA==",
+ "dependencies": {
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.6.3",
+ "long": "^5.2.1",
+ "lru-cache": "^7.14.1",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/mysql2/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/mysql2/node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
+ "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
+ "dependencies": {
+ "lru-cache": "^7.14.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/named-placeholders/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nodemon": {
+ "version": "2.0.22",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
+ "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^3.2.7",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^5.7.1",
+ "simple-update-notifier": "^1.0.7",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/nodemon/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/nodemon/node_modules/semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+ "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
+ "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
+ },
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+ "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/retry-as-promised": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz",
+ "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA=="
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
+ },
+ "node_modules/sequelize": {
+ "version": "6.29.3",
+ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.29.3.tgz",
+ "integrity": "sha512-iLbrN//Eh18zXIlNEUNQx7lk5R+SF39m+66bnrT3x8WB8sbxMH2hF4vw8RIa9ZzB1+c94rclMv/i8fngXmb/4A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sequelize"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.1.7",
+ "@types/validator": "^13.7.1",
+ "debug": "^4.3.3",
+ "dottie": "^2.0.2",
+ "inflection": "^1.13.2",
+ "lodash": "^4.17.21",
+ "moment": "^2.29.1",
+ "moment-timezone": "^0.5.35",
+ "pg-connection-string": "^2.5.0",
+ "retry-as-promised": "^7.0.3",
+ "semver": "^7.3.5",
+ "sequelize-pool": "^7.1.0",
+ "toposort-class": "^1.0.1",
+ "uuid": "^8.3.2",
+ "validator": "^13.7.0",
+ "wkx": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ibm_db": {
+ "optional": true
+ },
+ "mariadb": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "oracledb": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "pg-hstore": {
+ "optional": true
+ },
+ "snowflake-sdk": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ },
+ "tedious": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sequelize-pool": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz",
+ "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/sequelize/node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sequelize/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "dependencies": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
+ "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
+ "dependencies": {
+ "semver": "~7.0.0"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/simple-update-notifier/node_modules/semver": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+ "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/swagger-ui-dist": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.18.1.tgz",
+ "integrity": "sha512-n7AT4wzKIPpHy/BGflJOepGMrbY/7Cd5yVd9ptVczaJGAKScbVJrZxFbAE2ZSZa8KmqdQ0+pOs3/5mWY5tSMZQ=="
+ },
+ "node_modules/swagger-ui-express": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz",
+ "integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==",
+ "dependencies": {
+ "swagger-ui-dist": ">=4.11.0"
+ },
+ "engines": {
+ "node": ">= v0.10.32"
+ },
+ "peerDependencies": {
+ "express": ">=4.0.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/toposort-class": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz",
+ "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg=="
+ },
+ "node_modules/touch": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+ "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+ "dependencies": {
+ "nopt": "~1.0.10"
+ },
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.9.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz",
+ "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wkx": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
+ "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/yamljs": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz",
+ "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "glob": "^7.0.5"
+ },
+ "bin": {
+ "json2yaml": "bin/json2yaml",
+ "yaml2json": "bin/yaml2json"
+ }
+ }
+ }
+}
diff --git a/api/package.json b/api/package.json
new file mode 100644
index 0000000..5a7bcf0
--- /dev/null
+++ b/api/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "api",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "dev": "nodemon --legacy-watch -e js,json,yaml index.js",
+ "prod": "node index.js"
+ },
+ "author": "IndigoFox",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.3.4",
+ "cors": "^2.8.5",
+ "dotenv": "^16.0.3",
+ "express": "^4.18.2",
+ "express-bearer-token": "^2.4.0",
+ "express-jwt": "^8.4.1",
+ "mysql2": "^3.2.0",
+ "nodemon": "^2.0.22",
+ "sequelize": "^6.29.3",
+ "swagger-ui-express": "^4.6.2",
+ "yamljs": "^0.3.0"
+ }
+}
\ No newline at end of file
diff --git a/defaults/ranks.json b/defaults/ranks.json
new file mode 100644
index 0000000..2421f3b
--- /dev/null
+++ b/defaults/ranks.json
@@ -0,0 +1,134 @@
+[
+ {
+ "name": "Recruit",
+ "category": "Enlisted",
+ "sort_id": 22,
+ "image_url": "https://i.imgur.com/UE1Zs6g.png"
+ },
+ {
+ "name": "Private",
+ "category": "Enlisted",
+ "sort_id": 21,
+ "image_url": "http://i.imgur.com/Wh4nYns.png"
+ },
+ {
+ "name": "Private First Class",
+ "category": "Enlisted",
+ "sort_id": 20,
+ "image_url": "http://i.imgur.com/9V9PBDi.png"
+ },
+ {
+ "name": "Specialist",
+ "category": "Enlisted",
+ "sort_id": 19,
+ "image_url": "http://i.imgur.com/jEEuKKB.png"
+ },
+ {
+ "name": "Corporal",
+ "category": "NCO",
+ "sort_id": 18,
+ "image_url": "http://i.imgur.com/nfZrieG.png"
+ },
+ {
+ "name": "Sergeant",
+ "category": "NCO",
+ "sort_id": 17,
+ "image_url": "http://i.imgur.com/hfGy0ZZ.png"
+ },
+ {
+ "name": "Staff Sergeant",
+ "category": "NCO",
+ "sort_id": 16,
+ "image_url": "http://i.imgur.com/ZVg95ep.png"
+ },
+ {
+ "name": "Sergeant 1st Class",
+ "category": "NCO",
+ "sort_id": 15,
+ "image_url": ""
+ },
+ {
+ "name": "Master Sergeant",
+ "category": "NCO",
+ "sort_id": 14,
+ "image_url": ""
+ },
+ {
+ "name": "1st Sergeant",
+ "category": "NCO",
+ "sort_id": 13,
+ "image_url": ""
+ },
+ {
+ "name": "Sergeant Major",
+ "category": "NCO",
+ "sort_id": 12,
+ "image_url": ""
+ },
+ {
+ "name": "Warrant Officer 1",
+ "category": "Enlisted",
+ "sort_id": 11,
+ "image_url": ""
+ },
+ {
+ "name": "Chief Warrant Officer 2",
+ "category": "Enlisted",
+ "sort_id": 10,
+ "image_url": ""
+ },
+ {
+ "name": "Chief Warrant Officer 3",
+ "category": "NCO",
+ "sort_id": 9,
+ "image_url": ""
+ },
+ {
+ "name": "Chief Warrant Officer 4",
+ "category": "NCO",
+ "sort_id": 8,
+ "image_url": ""
+ },
+ {
+ "name": "Chief Warrant Officer 5",
+ "category": "NCO",
+ "sort_id": 7,
+ "image_url": ""
+ },
+ {
+ "name": "2nd Lieutenant",
+ "category": "Officer",
+ "sort_id": 6,
+ "image_url": ""
+ },
+ {
+ "name": "1st Lieutenant",
+ "category": "Officer",
+ "sort_id": 5,
+ "image_url": ""
+ },
+ {
+ "name": "Captain",
+ "category": "Officer",
+ "sort_id": 4,
+ "image_url": ""
+ },
+ {
+ "name": "Major",
+ "category": "Officer",
+ "sort_id": 3,
+ "image_url": ""
+ },
+ {
+ "name": "Lieutenant Colonel",
+ "category": "Officer",
+ "sort_id": 2,
+ "image_url": ""
+ },
+ {
+ "name": "Staff",
+ "category": "",
+ "sort_id": 1,
+ "image_url": ""
+ }
+]
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..f12df2a
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,63 @@
+version: '3.8'
+
+name: 17thrangers
+
+services:
+ db:
+ env_file: .env
+ image: mysql
+ # NOTE: use of "mysql_native_password" is not recommended: https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-password
+ # (this is just an example, not intended to be a production configuration)
+ command: --default-authentication-plugin=mysql_native_password
+ restart: always
+ environment:
+ MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
+ volumes:
+ - ./mysql:/var/lib/mysql
+ ports:
+ - 12730:3306
+
+ api:
+ env_file: .env
+ build:
+ context: ./api
+ dockerfile: Dockerfile
+ # use bind mount - make sure you go to ./api and run npm i first
+ volumes:
+ - ./api:/app
+ command: npm run dev
+ environment:
+ MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
+ MYSQL_DATABASE: ${DB_DATABASE}
+ MYSQL_USER: ${DB_USER}
+ MYSQL_PASSWORD: ${DB_PASSWORD}
+ restart: always
+ ports:
+ - 3000:3000
+ depends_on:
+ - db
+
+ # get self-signed cert
+ nginx:
+ env_file: .env
+ image: nginx:latest
+ restart: always
+ ports:
+ - 9229:9229
+ - 9230:9230
+ volumes:
+ - ./nginx/conf/:/etc/nginx/conf.d
+ # - ./certbot/www:/var/www/certbot/:ro
+ # - ./certbot/conf/:/etc/nginx/ssl/:ro
+
+ # my implementation, already have another primary instance
+ - /home/certbot/www:/var/www/certbot/:ro
+ - /home/certbot/conf/:/etc/nginx/ssl/:ro
+ depends_on:
+ - api
+
+ certbot:
+ image: certbot/certbot:latest
+ volumes:
+ - ./certbot/www/:/var/www/certbot/:rw
+ - ./certbot/conf/:/etc/letsencrypt/:rw
diff --git a/mysql/.gitkeep b/mysql/.gitkeep
new file mode 100644
index 0000000..08778d7
--- /dev/null
+++ b/mysql/.gitkeep
@@ -0,0 +1 @@
+{\rtf1}
\ No newline at end of file
diff --git a/nginx/conf/api.conf b/nginx/conf/api.conf
new file mode 100644
index 0000000..2f529ee
--- /dev/null
+++ b/nginx/conf/api.conf
@@ -0,0 +1,40 @@
+server {
+ listen 9229;
+ listen [::]:9229;
+
+ server_name example.org;
+ server_tokens off;
+
+ location /.well-known/acme-challenge/ {
+ root /var/www/certbot;
+ }
+
+ location / {
+ # redirect to 9230
+ return 301 https://example.org:9230$request_uri;
+ }
+
+}
+
+server {
+ listen 9230 default_server ssl http2;
+ listen [::]:9230 ssl http2;
+
+ server_name example.org;
+ ssl_certificate /etc/nginx/ssl/live/example.org/fullchain.pem;
+ ssl_certificate_key /etc/nginx/ssl/live/example.org/privkey.pem;
+ location / {
+ # send to api container
+ proxy_pass http://api:3000;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Port $server_port;
+ proxy_set_header X-Forwarded-Server $host;
+ proxy_set_header X-Forwarded-Uri $request_uri;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
\ No newline at end of file
diff --git a/nginx/conf/certmgmt.txt b/nginx/conf/certmgmt.txt
new file mode 100644
index 0000000..fa7546f
--- /dev/null
+++ b/nginx/conf/certmgmt.txt
@@ -0,0 +1,9 @@
+# DRY RUN - ensure certificates CAN be created
+docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ --dry-run -d example.org
+
+# PROD RUN - generate certificates for the provided site
+docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.org
+
+
+# RENEW CERTIFICATES - run every 3 months
+docker compose run --rm certbot renew
\ No newline at end of file