Browse Source

Init fresh web project

ghorsington 3 years ago
parent
commit
3f5aeec760
63 changed files with 1473 additions and 1623 deletions
  1. 162 162
      .gitignore
  2. 8 8
      .vscode/settings.json
  3. 5 1
      Makefile
  4. 41 41
      bot/.eslintrc.json
  5. 1 1
      facedetect/.dockerignore
  6. 14 14
      facedetect/Dockerfile
  7. 36 36
      facedetect/app/main.py
  8. 3 3
      facedetect/requirements.txt
  9. 15 15
      shared/src/db/entity/GuildGreeting.ts
  10. 12 12
      shared/src/db/entity/GuildViolationSettings.ts
  11. 34 34
      shared/src/db/entity/Violation.ts
  12. 86 0
      web/.eslintrc.json
  13. 6 6
      web/.gitignore
  14. 13 0
      web/.vscode/settings.json
  15. 0 27
      web/Dockerfile
  16. 21 0
      web/LICENSE
  17. 85 0
      web/README.md
  18. 0 4
      web/cypress.json
  19. 0 5
      web/cypress/fixtures/example.json
  20. 0 19
      web/cypress/integration/spec.js
  21. 0 17
      web/cypress/plugins/index.js
  22. 0 25
      web/cypress/support/commands.js
  23. 0 20
      web/cypress/support/index.js
  24. 70 64
      web/package.json
  25. 125 131
      web/rollup.config.js
  26. 0 5
      web/src/client.js
  27. 6 0
      web/src/client.ts
  28. 22 0
      web/src/components/ExampleComponent.svelte
  29. 0 54
      web/src/components/Nav.svelte
  30. 39 40
      web/src/routes/_error.svelte
  31. 24 16
      web/src/routes/_layout.svelte
  32. 0 7
      web/src/routes/about.svelte
  33. 0 48
      web/src/routes/dashboard/_layout.svelte
  34. 0 66
      web/src/routes/dashboard/contest/_components/AddContestForm.svelte
  35. 0 53
      web/src/routes/dashboard/contest/_components/ContestEntry.svelte
  36. 0 23
      web/src/routes/dashboard/contest/_components/ContestTable.svelte
  37. 0 26
      web/src/routes/dashboard/contest/_components/DurationInput.svelte
  38. 0 55
      web/src/routes/dashboard/contest/_components/EmojiSelector.svelte
  39. 0 63
      web/src/routes/dashboard/contest/index.svelte
  40. 0 8
      web/src/routes/dashboard/index.svelte
  41. 9 0
      web/src/routes/example.ts
  42. 46 14
      web/src/routes/index.svelte
  43. 0 72
      web/src/routes/login/discord/callback.ts
  44. 0 10
      web/src/routes/login/discord/do.ts
  45. 0 61
      web/src/routes/login/index.svelte
  46. 32 52
      web/src/server.ts
  47. 86 82
      web/src/service-worker.js
  48. 0 77
      web/src/style/main.css
  49. 35 35
      web/src/template.html
  50. 0 39
      web/src/typedefs/sapper.d.ts
  51. 0 6
      web/src/util/rpc_client.ts
  52. BIN
      web/static/apple-touch-icon-180.png
  53. BIN
      web/static/favicon.png
  54. BIN
      web/static/logo-192.png
  55. BIN
      web/static/logo-512.png
  56. 17 0
      web/static/main.css
  57. 38 20
      web/static/manifest.json
  58. 349 0
      web/static/normalize.css
  59. BIN
      web/static/screenshot-1.png
  60. BIN
      web/static/screenshot-2.png
  61. 12 0
      web/svelte.config.js
  62. 0 11
      web/tailwind.config.js
  63. 21 35
      web/tsconfig.json

+ 162 - 162
.gitignore

@@ -1,162 +1,162 @@
-build/
-data/
-lib/
-venv/
-
-gcloud_key.json
-
-__sapper__/
-.rpt2_cache/
-
-*.sqlite
-*.env
-
-# ---> Node
-# Logs
-logs
-*.log
-npm-debug.log*
-
-# Runtime data
-pids
-*.pid
-*.seed
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-
-# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (http://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directory
-# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
-node_modules
-
-token.js
-package-lock.json
-db.json
-imagestats.csv
-clarifai_keys.js
-.env
-db_old.json
-db_migrated.json
-*.old
-
-# Created by https://www.gitignore.io/api/python
-# Edit at https://www.gitignore.io/?templates=python
-
-### Python ###
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-#  Usually these files are written by a python script from a template
-#  before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# pyenv
-.python-version
-
-# pipenv
-#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-#   However, in case of collaboration, if having platform-specific dependencies or dependencies
-#   having no cross-platform support, pipenv may install dependencies that don't work, or not
-#   install all needed dependencies.
-#Pipfile.lock
-
-# celery beat schedule file
-celerybeat-schedule
-
-# SageMath parsed files
-*.sage.py
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# Mr Developer
-.mr.developer.cfg
-.project
-.pydevproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# End of https://www.gitignore.io/api/python
+build/
+data/
+lib/
+venv/
+
+gcloud_key.json
+
+__sapper__/
+.rpt2_cache/
+
+*.sqlite
+*.env
+
+# ---> Node
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
+node_modules
+
+token.js
+package-lock.json
+db.json
+imagestats.csv
+clarifai_keys.js
+.env
+db_old.json
+db_migrated.json
+*.old
+
+# Created by https://www.gitignore.io/api/python
+# Edit at https://www.gitignore.io/?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# Mr Developer
+.mr.developer.cfg
+.project
+.pydevproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# End of https://www.gitignore.io/api/python

+ 8 - 8
.vscode/settings.json

@@ -1,9 +1,9 @@
-{
-    "editor.codeActionsOnSave": {
-        "source.fixAll.eslint": true
-    },
-    "python.pythonPath": "/usr/bin/python3",
-    "python.linting.pylintArgs": [
-        "--generate-members"
-    ]
+{
+    "editor.codeActionsOnSave": {
+        "source.fixAll.eslint": true
+    },
+    "python.pythonPath": "/usr/bin/python3",
+    "python.linting.pylintArgs": [
+        "--generate-members"
+    ]
 }

+ 5 - 1
Makefile

@@ -27,4 +27,8 @@ start_bot: build
 	$(dc) up db adminer noctbot facedetect
 
 start_web: build
-	$(dc) up db adminer web
+	$(dc) up db adminer web
+
+npmi:
+	cd shared && npm install
+	cd bot && npm install

+ 41 - 41
bot/.eslintrc.json

@@ -1,41 +1,41 @@
-{
-    "env": {
-        "es6": true,
-        "node": true
-    },
-    "extends": [
-        "eslint:recommended",
-        "plugin:@typescript-eslint/eslint-recommended",
-        "plugin:@typescript-eslint/recommended"
-    ],
-    "globals": {
-        "Atomics": "readonly",
-        "SharedArrayBuffer": "readonly"
-    },
-    "parser": "@typescript-eslint/parser",
-    "parserOptions": {
-        "ecmaVersion": 11,
-        "sourceType": "module"
-    },
-    "plugins": [
-        "@typescript-eslint"
-    ],
-    "rules": {
-        "indent": [
-            "error",
-            4
-        ],
-        "linebreak-style": [
-            "error",
-            "windows"
-        ],
-        "quotes": [
-            "error",
-            "double"
-        ],
-        "semi": [
-            "error",
-            "always"
-        ]
-    }
-}
+{
+    "env": {
+        "es6": true,
+        "node": true
+    },
+    "extends": [
+        "eslint:recommended",
+        "plugin:@typescript-eslint/eslint-recommended",
+        "plugin:@typescript-eslint/recommended"
+    ],
+    "globals": {
+        "Atomics": "readonly",
+        "SharedArrayBuffer": "readonly"
+    },
+    "parser": "@typescript-eslint/parser",
+    "parserOptions": {
+        "ecmaVersion": 11,
+        "sourceType": "module"
+    },
+    "plugins": [
+        "@typescript-eslint"
+    ],
+    "rules": {
+        "indent": [
+            "error",
+            4
+        ],
+        "linebreak-style": [
+            "error",
+            "windows"
+        ],
+        "quotes": [
+            "error",
+            "double"
+        ],
+        "semi": [
+            "error",
+            "always"
+        ]
+    }
+}

+ 1 - 1
facedetect/.dockerignore

@@ -1,2 +1,2 @@
-venv
+venv
 __pycache__

+ 14 - 14
facedetect/Dockerfile

@@ -1,15 +1,15 @@
-FROM python:3.8-slim
-
-RUN apt-get update && \
-    apt-get -y install libglib2.0; \
-    apt-get clean
-
-WORKDIR /app
-COPY ./facedetect/requirements.txt .
-
-RUN pip install -r requirements.txt
-
-COPY ./facedetect .
-
-EXPOSE 80
+FROM python:3.8-slim
+
+RUN apt-get update && \
+    apt-get -y install libglib2.0; \
+    apt-get clean
+
+WORKDIR /app
+COPY ./facedetect/requirements.txt .
+
+RUN pip install -r requirements.txt
+
+COPY ./facedetect .
+
+EXPOSE 80
 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 

+ 36 - 36
facedetect/app/main.py

@@ -1,37 +1,37 @@
-import uvicorn
-from fastapi import FastAPI, File, UploadFile
-import cv2
-import numpy as np
-import os
-
-app = FastAPI()
-
-face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_alt2.xml")
-anime_cascade = cv2.CascadeClassifier(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "animu.xml"))
-
-@app.post("/process")
-def process(img_data: bytes = File(None)):
-    try:
-        im = cv2.imdecode(np.frombuffer(img_data, np.uint8), cv2.IMREAD_COLOR)
-        gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
-        normGray = cv2.equalizeHist(gray)
-
-        animeFaces = anime_cascade.detectMultiScale(normGray,
-                                                1.1,
-                                                5,
-                                                0,
-                                                (24, 24))
-        normalFaces = face_cascade.detectMultiScale(normGray)
-        return {
-            "ok": True,
-            "animeFaces": [{"x": x.item(), "y": y.item(), "w": w.item(), "h": h.item()} for (x, y, w, h) in animeFaces],
-            "normalFaces": [{"x": x.item(), "y": y.item(), "w": w.item(), "h": h.item()} for (x, y, w, h) in normalFaces]
-        }
-    except Exception as err:
-        return {
-            "ok": False,
-            "error": str(err)
-        }
-
-if __name__ == "__main__":
+import uvicorn
+from fastapi import FastAPI, File, UploadFile
+import cv2
+import numpy as np
+import os
+
+app = FastAPI()
+
+face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_alt2.xml")
+anime_cascade = cv2.CascadeClassifier(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "animu.xml"))
+
+@app.post("/process")
+def process(img_data: bytes = File(None)):
+    try:
+        im = cv2.imdecode(np.frombuffer(img_data, np.uint8), cv2.IMREAD_COLOR)
+        gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
+        normGray = cv2.equalizeHist(gray)
+
+        animeFaces = anime_cascade.detectMultiScale(normGray,
+                                                1.1,
+                                                5,
+                                                0,
+                                                (24, 24))
+        normalFaces = face_cascade.detectMultiScale(normGray)
+        return {
+            "ok": True,
+            "animeFaces": [{"x": x.item(), "y": y.item(), "w": w.item(), "h": h.item()} for (x, y, w, h) in animeFaces],
+            "normalFaces": [{"x": x.item(), "y": y.item(), "w": w.item(), "h": h.item()} for (x, y, w, h) in normalFaces]
+        }
+    except Exception as err:
+        return {
+            "ok": False,
+            "error": str(err)
+        }
+
+if __name__ == "__main__":
     uvicorn.run(app, host="0.0.0.0", port=8081)

+ 3 - 3
facedetect/requirements.txt

@@ -1,4 +1,4 @@
-opencv-python-headless==4.2.0.34
-python-multipart==0.0.5
-fastapi==0.55.1
+opencv-python-headless==4.2.0.34
+python-multipart==0.0.5
+fastapi==0.55.1
 uvicorn==0.11.5

+ 15 - 15
shared/src/db/entity/GuildGreeting.ts

@@ -1,16 +1,16 @@
-import { Entity, PrimaryColumn, Column } from "typeorm";
-
-@Entity()
-export class GuildGreeting {
-    @PrimaryColumn()
-    guildId: string;
-
-    @Column()
-    greetingChannelId: string;
-
-    @Column({type: "text"})
-    onJoinMessage: string;
-
-    @Column({type: "text"})
-    onLeaveMessage: string;
+import { Entity, PrimaryColumn, Column } from "typeorm";
+
+@Entity()
+export class GuildGreeting {
+    @PrimaryColumn()
+    guildId: string;
+
+    @Column()
+    greetingChannelId: string;
+
+    @Column({type: "text"})
+    onJoinMessage: string;
+
+    @Column({type: "text"})
+    onLeaveMessage: string;
 }

+ 12 - 12
shared/src/db/entity/GuildViolationSettings.ts

@@ -1,13 +1,13 @@
-import { Entity, PrimaryColumn, Column } from "typeorm";
-
-@Entity()
-export class GuildViolationSettings {
-    @PrimaryColumn()
-    guildId: string;
-
-    @Column({ nullable: true })
-    violationInfoChannelId?: string;
-
-    @Column({ nullable: true })
-    muteRoleId: string;
+import { Entity, PrimaryColumn, Column } from "typeorm";
+
+@Entity()
+export class GuildViolationSettings {
+    @PrimaryColumn()
+    guildId: string;
+
+    @Column({ nullable: true })
+    violationInfoChannelId?: string;
+
+    @Column({ nullable: true })
+    muteRoleId: string;
 }

+ 34 - 34
shared/src/db/entity/Violation.ts

@@ -1,35 +1,35 @@
-import { Entity, Column, PrimaryGeneratedColumn, ChildEntity, TableInheritance } from "typeorm";
-
-@Entity()
-@TableInheritance({column: { type: "varchar", name: "type" }})
-export abstract class Violation {
-    @PrimaryGeneratedColumn()
-    id: number;
-
-    @Column()
-    guildId: string;
-
-    @Column()
-    userId: string;
-
-    @Column({ type: "text", nullable: true })
-    reason?: string;
-
-    @Column()
-    valid: boolean;
-}
-
-@ChildEntity()
-export class Mute extends Violation {
-    @Column()
-    endsAt: Date;
-}
-
-@ChildEntity()
-export class Ban extends Violation {
-    @Column()
-    endsAt: Date;
-}
-
-@ChildEntity()
+import { Entity, Column, PrimaryGeneratedColumn, ChildEntity, TableInheritance } from "typeorm";
+
+@Entity()
+@TableInheritance({column: { type: "varchar", name: "type" }})
+export abstract class Violation {
+    @PrimaryGeneratedColumn()
+    id: number;
+
+    @Column()
+    guildId: string;
+
+    @Column()
+    userId: string;
+
+    @Column({ type: "text", nullable: true })
+    reason?: string;
+
+    @Column()
+    valid: boolean;
+}
+
+@ChildEntity()
+export class Mute extends Violation {
+    @Column()
+    endsAt: Date;
+}
+
+@ChildEntity()
+export class Ban extends Violation {
+    @Column()
+    endsAt: Date;
+}
+
+@ChildEntity()
 export class Kick extends Violation {}

+ 86 - 0
web/.eslintrc.json

@@ -0,0 +1,86 @@
+{
+	"extends": [
+		"eslint:recommended",
+		"airbnb-base"
+	],
+	"parserOptions": {
+		"ecmaVersion": 11,
+		"sourceType": "module",
+		"project": "./tsconfig.json"
+	},
+	"env": {
+		"node": true,
+		"browser": true,
+		"es2020": true
+	},
+	"rules": {
+		"class-methods-use-this": "off",
+		"import/extensions": [
+			"error",
+			"ignorePackages",
+			{
+				"js": "never",
+				"ts": "never"
+			}
+		],
+		"import/prefer-default-export": "off",
+		"import/no-extraneous-dependencies": "off",
+		"indent": [
+			"error",
+			"tab"
+		],
+		"no-console": "off",
+		"no-tabs": [
+			"error",
+			{
+				"allowIndentationTabs": true
+			}
+		],
+		"no-unused-vars": [
+			"error", 
+			{ 
+				"argsIgnorePattern": "^_",
+				"varsIgnorePattern": "^_"
+			}
+		],
+		"quotes": [
+			"error",
+			"double"
+		]
+	},
+	"overrides": [
+		{
+			"files": [
+				"*.ts"
+			],
+			"parser": "@typescript-eslint/parser",
+			"extends": [
+				"plugin:import/typescript",
+				"plugin:@typescript-eslint/recommended"
+			],
+			"plugins": [
+				"@typescript-eslint"
+			],
+			"rules": {
+				"@typescript-eslint/ban-ts-comment": "off"
+			}
+		},
+		{
+			"files": [
+				"*.svelte"
+			],
+			"plugins": [
+				"svelte3"
+			],
+			"processor": "svelte3/svelte3",
+			"rules": {
+				"import/first": "off",
+				"import/no-duplicates": "off",
+				"import/no-mutable-exports": "off",
+				"import/no-mutable-unresolved": "off",
+				"no-undef": "off",
+				"no-unused-vars": "off"
+			}
+		}
+	]
+}

+ 6 - 6
web/.gitignore

@@ -1,6 +1,6 @@
-.DS_Store
-/node_modules/
-/src/node_modules/@sapper/
-yarn-error.log
-/cypress/screenshots/
-/__sapper__/
+._*
+.DS_Store
+/.history/
+node_modules/
+__sapper__/
+/src/node_modules/@sapper/

+ 13 - 0
web/.vscode/settings.json

@@ -0,0 +1,13 @@
+{
+	"editor.codeActionsOnSave": {
+		"source.fixAll": true
+	},
+	"[javascript]": {
+		"editor.defaultFormatter": "dbaeumer.vscode-eslint"
+	},
+	"[typescript]": {
+		"editor.defaultFormatter": "dbaeumer.vscode-eslint"
+	},
+	"eslint.format.enable": true,
+	"eslint.lintTask.enable": true,
+}

+ 0 - 27
web/Dockerfile

@@ -1,27 +0,0 @@
-FROM node:14-alpine
-
-RUN apk --no-cache add make
-
-WORKDIR /app
-
-COPY ./shared/package.json ./shared/package.json
-WORKDIR /app/shared
-RUN npm install
-
-WORKDIR /app
-
-COPY ./web/package.json ./web/package.json
-WORKDIR /app/web
-RUN npm install
-
-WORKDIR /app
-
-COPY ./shared ./shared
-COPY ./web ./web
-COPY ./Makefile ./Makefile
-
-RUN make build_web
-
-WORKDIR /app/web
-EXPOSE 3000
-CMD npm start

+ 21 - 0
web/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Jacob Babich
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

File diff suppressed because it is too large
+ 85 - 0
web/README.md


+ 0 - 4
web/cypress.json

@@ -1,4 +0,0 @@
-{
-	"baseUrl": "http://localhost:3000",
-	"video": false
-}

+ 0 - 5
web/cypress/fixtures/example.json

@@ -1,5 +0,0 @@
-{
-  "name": "Using fixtures to represent data",
-  "email": "hello@cypress.io",
-  "body": "Fixtures are a great way to mock data for responses to routes"
-}

+ 0 - 19
web/cypress/integration/spec.js

@@ -1,19 +0,0 @@
-describe('Sapper template app', () => {
-	beforeEach(() => {
-		cy.visit('/')
-	});
-
-	it('has the correct <h1>', () => {
-		cy.contains('h1', 'Great success!')
-	});
-
-	it('navigates to /about', () => {
-		cy.get('nav a').contains('about').click();
-		cy.url().should('include', '/about');
-	});
-
-	it('navigates to /blog', () => {
-		cy.get('nav a').contains('blog').click();
-		cy.url().should('include', '/blog');
-	});
-});

+ 0 - 17
web/cypress/plugins/index.js

@@ -1,17 +0,0 @@
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
-// This function is called when a project is opened or re-opened (e.g. due to
-// the project's config changing)
-
-module.exports = (on, config) => {
-  // `on` is used to hook into various events Cypress emits
-  // `config` is the resolved Cypress config
-}

+ 0 - 25
web/cypress/support/commands.js

@@ -1,25 +0,0 @@
-// ***********************************************
-// This example commands.js shows you how to
-// create various custom commands and overwrite
-// existing commands.
-//
-// For more comprehensive examples of custom
-// commands please read more here:
-// https://on.cypress.io/custom-commands
-// ***********************************************
-//
-//
-// -- This is a parent command --
-// Cypress.Commands.add("login", (email, password) => { ... })
-//
-//
-// -- This is a child command --
-// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
-//
-//
-// -- This is a dual command --
-// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
-//
-//
-// -- This is will overwrite an existing command --
-// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

+ 0 - 20
web/cypress/support/index.js

@@ -1,20 +0,0 @@
-// ***********************************************************
-// This example support/index.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-// Import commands.js using ES2015 syntax:
-import './commands'
-
-// Alternatively you can use CommonJS syntax:
-// require('./commands')

+ 70 - 64
web/package.json

@@ -1,64 +1,70 @@
-{
-  "name": "noctbot-web",
-  "description": "NoctBot web frontend",
-  "version": "0.0.1",
-  "scripts": {
-    "dev": "sapper dev",
-    "build": "sapper build --legacy",
-    "export": "sapper export --legacy",
-    "start": "node __sapper__/build",
-    "cy:run": "cypress run",
-    "cy:open": "cypress open",
-    "test": "run-p --race dev cy:run"
-  },
-  "dependencies": {
-    "@types/btoa": "^1.2.3",
-    "@types/compression": "0.0.36",
-    "@types/cookie-session": "^2.0.37",
-    "@types/dotenv": "^6.1.1",
-    "@types/express": "^4.17.0",
-    "@types/node-fetch": "^2.5.0",
-    "@types/request-promise-native": "^1.0.16",
-    "autoprefixer": "^9.6.1",
-    "btoa": "^1.2.1",
-    "bulma": "^0.7.5",
-    "compression": "^1.7.1",
-    "cookie-session": "^1.3.3",
-    "dotenv": "^8.0.0",
-    "express": "^4.17.1",
-    "node-fetch": "^2.6.0",
-    "pg": "^7.11.0",
-    "polka": "^0.5.0",
-    "postcss-import": "^12.0.1",
-    "postcss-nested": "^4.1.2",
-    "request": "^2.88.0",
-    "request-promise-native": "^1.0.7",
-    "sirv": "^0.4.0",
-    "tailwindcss": "^1.1.2",
-    "typeorm": "^0.2.18",
-    "typescript-rest-rpc": "^1.0.10"
-  },
-  "devDependencies": {
-    "@babel/core": "^7.0.0",
-    "@babel/plugin-syntax-dynamic-import": "^7.0.0",
-    "@babel/plugin-transform-runtime": "^7.0.0",
-    "@babel/preset-env": "^7.0.0",
-    "@babel/runtime": "^7.0.0",
-    "node-sass": "^4.12.0",
-    "npm-run-all": "^4.1.5",
-    "postcss": "^7.0.17",
-    "rollup": "^1.12.0",
-    "rollup-plugin-babel": "^4.0.2",
-    "rollup-plugin-commonjs": "^10.0.0",
-    "rollup-plugin-node-resolve": "^5.2.0",
-    "rollup-plugin-replace": "^2.0.0",
-    "rollup-plugin-svelte": "^5.0.1",
-    "rollup-plugin-terser": "^4.0.4",
-    "rollup-plugin-typescript2": "^0.22.0",
-    "sapper": "^0.27.0",
-    "svelte": "^3.0.0",
-    "svelte-preprocess": "^2.15.1",
-    "svelte-preprocess-sass": "^0.2.0",
-    "typescript": "^3.5.3"
-  }
-}
+{
+	"name": "sapper-typescript-graphql-template",
+	"description": "A template that includes Svelte with Sapper, TypeScript preprocessing, and a GraphQL server through TypeGraphQL",
+	"keywords": [
+		"sapper",
+		"typegraphql",
+		"typescript",
+		"eslint",
+		"svelte",
+		"apollo-server",
+		"graphql"
+	],
+	"homepage": "https://github.com/babichjacob/sapper-typescript-graphql-template",
+	"repository": {
+		"type": "git",
+		"url": "https://github.com/babichjacob/sapper-typescript-graphql-template.git"
+	},
+	"license": "MIT",
+	"version": "2020.06.05",
+	"scripts": {
+		"eslint": "eslint",
+		"eslint:fix": "eslint --fix ./*.js ./src/*.ts ./src/components/**/*.svelte ./src/graphql/**/*.ts ./src/routes/**/*.svelte ./src/routes/**/*.ts",
+		"dev": "sapper dev",
+		"build": "cross-env NODE_ENV=production sapper build --legacy",
+		"prod": "npm run build",
+		"export": "cross-env NODE_ENV=production sapper export --legacy",
+		"start": "node __sapper__/build"
+	},
+	"dependencies": {
+		"compression": "^1.7.4",
+		"express": "^4.17.1",
+		"node-fetch": "^2.6.0",
+		"sirv": "^0.4.6"
+	},
+	"devDependencies": {
+		"@babel/core": "^7.11.0",
+		"@babel/plugin-syntax-dynamic-import": "^7.0.0",
+		"@babel/plugin-transform-runtime": "^7.11.0",
+		"@babel/preset-env": "^7.11.0",
+		"@babel/runtime": "^7.11.0",
+		"@rollup/plugin-babel": "^5.1.0",
+		"@rollup/plugin-commonjs": "^13.0.2",
+		"@rollup/plugin-json": "^4.1.0",
+		"@rollup/plugin-node-resolve": "^8.4.0",
+		"@rollup/plugin-replace": "^2.3.3",
+		"@rollup/plugin-typescript": "^4.1.2",
+		"@types/compression": "^1.7.0",
+		"@types/express": "^4.17.7",
+		"@types/node-fetch": "^2.5.7",
+		"@typescript-eslint/eslint-plugin": "^3.7.1",
+		"@typescript-eslint/parser": "^3.7.1",
+		"bufferutil": "^4.0.1",
+		"class-validator": "^0.12.2",
+		"cross-env": "^7.0.2",
+		"eslint": "^7.6.0",
+		"eslint-config-airbnb-base": "^14.2.0",
+		"eslint-plugin-import": "^2.22.0",
+		"eslint-plugin-svelte3": "^2.7.3",
+		"reflect-metadata": "^0.1.13",
+		"rollup": "^2.23.0",
+		"rollup-plugin-svelte": "^5.2.3",
+		"rollup-plugin-terser": "^6.1.0",
+		"sapper": "^0.27.16",
+		"svelte": "^3.24.0",
+		"svelte-preprocess": "^3.9.12",
+		"tslib": "^2.0.0",
+		"typescript": "^3.9.7",
+		"utf-8-validate": "^5.0.2"
+	}
+}

+ 125 - 131
web/rollup.config.js

@@ -1,131 +1,125 @@
-import resolve from 'rollup-plugin-node-resolve';
-import replace from 'rollup-plugin-replace';
-import commonjs from 'rollup-plugin-commonjs';
-import svelte from 'rollup-plugin-svelte';
-import babel from 'rollup-plugin-babel';
-import { terser } from 'rollup-plugin-terser';
-import config from 'sapper/config/rollup.js';
-import pkg from './package.json';
-import typescript from 'rollup-plugin-typescript2';
-import autoPreprocess from 'svelte-preprocess';
-
-const mode = process.env.NODE_ENV;
-const dev = mode === 'development';
-const legacy = !!process.env.SAPPER_LEGACY_BUILD;
-
-const onwarn = (warning, onwarn) => (warning.code === 'CIRCULAR_DEPENDENCY' && /[/\\]@sapper[/\\]/.test(warning.message)) || onwarn(warning);
-const dedupe = importee => importee === 'svelte' || importee.startsWith('svelte/');
-
-
-const preprocessOptions = {
-	scss: false,
-	transformers: {
-		// scss: {
-		// 	includePaths: [
-		// 		'node_modules',
-		// 		'src'
-		// 	]
-		// },
-		postcss: {
-			plugins: [
-				require('postcss-import'),
-				require('tailwindcss'),
-				require('postcss-nested'),
-				require('autoprefixer'),
-			]
-		}
-	},
-}
-
-export default {
-	client: {
-		input: config.client.input(),
-		output: config.client.output(),
-		plugins: [
-			replace({
-				'process.browser': true,
-				'process.env.NODE_ENV': JSON.stringify(mode)
-			}),
-			svelte({
-				dev,
-				hydratable: true,
-				emitCss: true,
-				preprocess: autoPreprocess(preprocessOptions)
-			}),
-			resolve({
-				browser: true,
-				dedupe
-			}),
-			commonjs(),
-
-			legacy && babel({
-				extensions: ['.js', '.mjs', '.html', '.svelte'],
-				runtimeHelpers: true,
-				exclude: ['node_modules/@babel/**'],
-				presets: [
-					['@babel/preset-env', {
-						targets: '> 0.25%, not dead'
-					}]
-				],
-				plugins: [
-					'@babel/plugin-syntax-dynamic-import',
-					['@babel/plugin-transform-runtime', {
-						useESModules: true
-					}]
-				]
-			}),
-
-			!dev && terser({
-				module: true
-			})
-		],
-
-		onwarn,
-	},
-
-	server: {
-		input: { server: config.server.input().server.replace(/\.js$/, '.ts') },
-		output: {
-			...config.server.output(),
-			sourcemap: process.env.NODE_ENV == "development"
-		},
-		plugins: [
-			replace({
-				'process.browser': false,
-				'process.env.NODE_ENV': JSON.stringify(mode)
-			}),
-			svelte({
-				generate: 'ssr',
-				dev,
-				preprocess: autoPreprocess(preprocessOptions)
-			}),
-			resolve({
-				dedupe,
-				extensions: ['.mjs', '.js', '.ts', '.json']
-			}),
-			commonjs(),
-			typescript()
-		],
-		external: Object.keys(pkg.dependencies).concat(
-			require('module').builtinModules || Object.keys(process.binding('natives'))
-		),
-
-		onwarn,
-	},
-
-	serviceworker: {
-		input: config.serviceworker.input(),
-		output: config.serviceworker.output(),
-		plugins: [
-			resolve(),
-			replace({
-				'process.browser': true,
-				'process.env.NODE_ENV': JSON.stringify(mode)
-			}),
-			commonjs(),
-			!dev && terser()
-		],
-
-		onwarn,
-	}
-};
+import resolve from "@rollup/plugin-node-resolve";
+import replace from "@rollup/plugin-replace";
+import commonjs from "@rollup/plugin-commonjs";
+import json from "@rollup/plugin-json";
+import typescript from "@rollup/plugin-typescript";
+import svelte from "rollup-plugin-svelte";
+import babel from "@rollup/plugin-babel";
+import { terser } from "rollup-plugin-terser";
+import config from "sapper/config/rollup";
+import pkg from "./package.json";
+import { preprocess as sveltePreprocessConfig } from "./svelte.config";
+
+const preprocess = [
+	sveltePreprocessConfig,
+	// You could have more preprocessors, like MDsveX
+];
+
+const mode = process.env.NODE_ENV;
+const dev = mode === "development";
+const sourcemap = dev ? "inline" : false;
+const legacy = !!process.env.SAPPER_LEGACY_BUILD;
+
+const warningIsIgnored = (warning) => warning.message.includes(
+	"Use of eval is strongly discouraged, as it poses security risks and may cause issues with minification",
+) || warning.message.includes("Circular dependency: node_modules");
+
+// Workaround for https://github.com/sveltejs/sapper/issues/1221
+const onwarn = (warning, _onwarn) => (warning.code === "CIRCULAR_DEPENDENCY" && /[/\\]@sapper[/\\]/.test(warning.message)) || warningIsIgnored(warning) || console.warn(warning.toString());
+
+export default {
+	client: {
+		input: config.client.input().replace(/\.js$/, ".ts"),
+		output: config.client.output(),
+		plugins: [
+			replace({
+				"process.browser": true,
+				"process.env.NODE_ENV": JSON.stringify(mode),
+			}),
+			svelte({
+				dev,
+				hydratable: true,
+				emitCss: true,
+				preprocess,
+			}),
+			resolve({
+				browser: true,
+				dedupe: ["svelte"],
+			}),
+			commonjs(),
+			typescript(),
+			json(),
+
+			legacy && babel({
+				extensions: [".js", ".mjs", ".html", ".svelte"],
+				babelHelpers: "runtime",
+				exclude: ["node_modules/@babel/**"],
+				presets: [
+					["@babel/preset-env", {
+						targets: "> 0.25%, not dead",
+					}],
+				],
+				plugins: [
+					"@babel/plugin-syntax-dynamic-import",
+					["@babel/plugin-transform-runtime", {
+						useESModules: true,
+					}],
+				],
+			}),
+
+			!dev && terser({
+				module: true,
+			}),
+		],
+
+		preserveEntrySignatures: false,
+		onwarn,
+	},
+
+	server: {
+		input: { server: config.server.input().server.replace(/\.js$/, ".ts") },
+		output: { ...config.server.output(), sourcemap },
+		plugins: [
+			replace({
+				"process.browser": false,
+				"process.env.NODE_ENV": JSON.stringify(mode),
+				"module.require": "require",
+			}),
+			svelte({
+				generate: "ssr",
+				dev,
+				preprocess,
+			}),
+			resolve({
+				dedupe: ["svelte"],
+			}),
+			commonjs(),
+			typescript(),
+			json(),
+		],
+		external: Object.keys(pkg.dependencies).concat(
+			require("module").builtinModules || Object.keys(process.binding("natives")), // eslint-disable-line global-require
+		),
+
+		preserveEntrySignatures: "strict",
+		onwarn,
+	},
+
+	serviceworker: {
+		input: config.serviceworker.input().replace(/\.js$/, ".ts"),
+		output: config.serviceworker.output(),
+		plugins: [
+			resolve(),
+			replace({
+				"process.browser": true,
+				"process.env.NODE_ENV": JSON.stringify(mode),
+			}),
+			commonjs(),
+			typescript(),
+			!dev && terser(),
+		],
+
+		preserveEntrySignatures: false,
+		onwarn,
+	},
+};

+ 0 - 5
web/src/client.js

@@ -1,5 +0,0 @@
-import * as sapper from '@sapper/app';
-
-sapper.start({
-	target: document.querySelector('#sapper')
-});

+ 6 - 0
web/src/client.ts

@@ -0,0 +1,6 @@
+// @ts-ignore -- generated package
+import * as sapper from "@sapper/app"; // eslint-disable-line import/no-unresolved
+
+sapper.start({
+	target: document.querySelector("#sapper"),
+});

+ 22 - 0
web/src/components/ExampleComponent.svelte

@@ -0,0 +1,22 @@
+<script lang="typescript">
+	export let title: string;
+	export let paragraph: string;
+</script>
+
+<svelte:options immutable />
+
+<style>
+	h1 {
+		color: #4299E1;
+		font-size: 1.875rem;
+		font-weight: 200;
+	}
+
+	p {
+		color: #4A5568;
+		margin-top: 1rem;
+	}
+</style>
+
+<h1>{title}</h1>
+<p>{paragraph}</p>

+ 0 - 54
web/src/components/Nav.svelte

@@ -1,54 +0,0 @@
-<script>
-  import { stores } from "@sapper/app";
-  let { session } = stores();
-  export let segment;
-</script>
-
-<style lang="css">
-  nav {
-    @apply .w-2/12 .bg-gray-800 .rounded-l-sm .overflow-y-auto .flex .flex-col;
-  }
-
-  .user-box {
-    @apply .flex .items-center p-2 bg-gray-dark;
-
-    & > img {
-      @apply .w-1/5 .rounded-full;
-    }
-
-    & > div {
-      @apply .m-2 .font-bold .text-gray-100;
-    }
-  }
-
-  .button-list {
-    @apply .flex .flex-col .m-2 p-2;
-
-    & > h2 {
-      @apply .text-lg .my-1 .text-white .font-bold .uppercase .mt-2;
-    }
-
-    & > a {
-      @apply .bg-gray-800 .px-2 .py-1 .rounded-sm .font-medium;
-
-      &:hover {
-        @apply .bg-gray-700;
-      }
-
-      &[data-active="true"] {
-        @apply .bg-blue-800;
-      }
-    }
-  }
-</style>
-
-<nav role="navigation">
-  <div class="user-box">
-    <img alt="User Avatar" src={$session.user.avatarURL} />
-    <div>{$session.user.username}</div>
-  </div>
-  <div class="button-list">
-    <h2>Features</h2>
-    <a data-active={segment === 'contest'} href="dashboard/contest">Contest</a>
-  </div>
-</nav>

+ 39 - 40
web/src/routes/_error.svelte

@@ -1,40 +1,39 @@
-<script>
-	export let status;
-	export let error;
-
-	const dev = process.env.NODE_ENV === 'development';
-</script>
-
-<style>
-	h1, p {
-		margin: 0 auto;
-	}
-
-	h1 {
-		font-size: 2.8em;
-		font-weight: 700;
-		margin: 0 0 0.5em 0;
-	}
-
-	p {
-		margin: 1em auto;
-	}
-
-	@media (min-width: 480px) {
-		h1 {
-			font-size: 4em;
-		}
-	}
-</style>
-
-<svelte:head>
-	<title>{status}</title>
-</svelte:head>
-
-<h1>{status}</h1>
-
-<p>{error.message}</p>
-
-{#if dev && error.stack}
-	<pre>{error.stack}</pre>
-{/if}
+<script lang="typescript">
+	export let status: string;
+	export let error: Error;
+
+	// @ts-ignore -- creates a warning when the left side of the === is replaced with "production" by rollup-plugin-replace in `rollup.config.js`
+	const dev = process.env.NODE_ENV === "development";
+</script>
+
+<style>
+	section {
+		flex: 1 1 0%;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+	}
+	
+	h1, h2 {
+		color: #C53030;
+	}
+
+	h1 {
+		margin-top: 0.25rem;
+		font-size: 1.5rem;
+	}
+
+	h2 {
+		font-size: 1.125rem;
+	}
+</style>
+
+<section>
+	<h1>{error.message}</h1>
+	<h2>{status}</h2>
+</section>
+
+{#if dev && error.stack}
+	<pre>{error.stack}</pre>
+{/if}

+ 24 - 16
web/src/routes/_layout.svelte

@@ -1,16 +1,24 @@
-<script context="module">
-  export async function preload(page, session) {
-    const { user } = session;
-    if (!session.user && !page.path.startsWith("/login"))
-      return this.redirect(302, "login");
-    return { user };
-  }
-</script>
-
-<style global lang="css">
-  @import "../style/main.css";
-</style>
-
-<main>
-  <slot />
-</main>
+<script lang="typescript">
+	// @ts-ignore -- generated package
+	import { stores } from "@sapper/app"; // eslint-disable-line import/no-unresolved
+
+	// You may not want to use `segment`, but it is passed for the time being and will
+	// create a warning if not expected: https://github.com/sveltejs/sapper-template/issues/210
+	// https://github.com/sveltejs/sapper/issues/824
+	export let segment: string = "";
+	// Silence unused export property warning
+	if (segment) {};
+
+	const { page } = stores();
+
+	let path: string;
+	$: path = $page.path.slice(1);
+</script>
+
+<svelte:head>
+	<title>
+		{path ? path.charAt(0).toUpperCase() + path.slice(1) : "Index"}
+	</title>
+</svelte:head>
+
+<slot />

+ 0 - 7
web/src/routes/about.svelte

@@ -1,7 +0,0 @@
-<svelte:head>
-	<title>About</title>
-</svelte:head>
-
-<h1>About this site</h1>
-
-<p>This is the 'about' page. There's not much here.</p>

+ 0 - 48
web/src/routes/dashboard/_layout.svelte

@@ -1,48 +0,0 @@
-<script>
-  import Nav from "../../components/Nav.svelte";
-  export let segment;
-</script>
-
-<style lang="css">
-  .frame {
-    @apply w-screen h-screen m-auto .flex .items-center .justify-center;
-
-    & > div {
-      @apply w-full h-full;
-    }
-  }
-
-  @screen xl {
-    .frame {
-      & > div {
-        @apply .w-10/12;
-        height: calc(10 / 12 * 100%);
-      }
-    }
-  }
-
-  .button {
-    @apply .bg-gray-800 .px-2 .py-1 .rounded-sm .font-medium;
-
-    &:hover {
-      @apply .bg-gray-700;
-    }
-
-    &[data-active="true"] {
-      @apply .bg-blue-800;
-    }
-  }
-
-  .main-view {
-    @apply .bg-gray-700 .w-10/12 .overflow-y-auto .rounded-r-sm .p-2;
-  }
-</style>
-
-<div class="frame">
-  <div class="flex shadow-big">
-    <Nav {segment} />
-    <section class="main-view ">
-      <slot />
-    </section>
-  </div>
-</div>

+ 0 - 66
web/src/routes/dashboard/contest/_components/AddContestForm.svelte

@@ -1,66 +0,0 @@
-<script>
-  import EmojiSelector from "./EmojiSelector.svelte";
-  import DurationInput from "./DurationInput.svelte";
-
-  import { createEventDispatcher } from "svelte";
-  const dispatch = createEventDispatcher();
-
-  function submit(e) {
-    e.preventDefault();
-  }
-</script>
-
-<style>
-  div.emoji-selector {
-    @apply .flex;
-
-    & input[type="radio"] {
-      @apply hidden;
-    }
-  }
-
-  input[type="submit"] {
-    flex-grow: 0 !important;
-  }
-
-  span.info {
-    @əpply italic pl-10;
-  }
-</style>
-
-<form class="form-ctrl" on:submit={submit}>
-  <div>
-    <label for="channel-id">Channel</label>
-    <select id="channel-id">
-      <option value="01312">#test</option>
-      <option value="01313">#test2</option>
-      <option value="01314">#test3</option>
-    </select>
-  </div>
-  <div>
-    <label for="entry-duration">Entry duration</label>
-    <DurationInput id="entry-duration" unitId="entry-duration-unit" />
-  </div>
-  <div>
-    <label for="voting-duration">Voting duration</label>
-    <DurationInput id="voting-duration" unitId="voting-duration-unit" />
-  </div>
-  <div>
-    <div>Voting emoji</div>
-    <EmojiSelector />
-  </div>
-  <div>
-    <label for="max-winners">Max winners</label>
-    <input
-      id="max-winners"
-      name="max-winners"
-      type="number"
-      step="1"
-      min="1"
-      value="1" />
-  </div>
-  <div>
-    <div />
-    <input class="btn" type="submit" value="Create competition" />
-  </div>
-</form>

+ 0 - 53
web/src/routes/dashboard/contest/_components/ContestEntry.svelte

@@ -1,53 +0,0 @@
-<script>
-  export let contest = {};
-  export let index = 0;
-  let visible = false;
-
-  function toggleView() {
-    visible = !visible;
-  }
-</script>
-
-<style>
-  h3 {
-    @apply .font-bold;
-  }
-</style>
-
-<tr class="entry" data-nth={index % 2 == 0 ? 'even' : 'odd'}>
-  <td>{contest.id}</td>
-  <td>
-    <pre>{contest.channel}</pre>
-  </td>
-  <td>{contest.started.toISOString()}</td>
-  <td>{contest.entryDuration}</td>
-  <td>{contest.voteDuration}</td>
-  <td>
-    <button class="btn" on:click={toggleView}>View info and actions</button>
-  </td>
-</tr>
-
-{#if visible}
-  <tr class="info">
-    <td colspan="6">
-      <h3>Actions</h3>
-      <div class="py-1 pb-4">
-        <button class="btn" disabled="disabled">End entry phase</button>
-        <button class="btn">Start voting</button>
-        <button class="btn">End vote phase</button>
-        <button class="btn">Announce winners</button>
-      </div>
-
-      {#if contest.winners}
-        <h3>Winners</h3>
-        <div class="py-1">
-          <ol class="list-decimal list-inside">
-            {#each contest.winners as winner}
-              <li>{winner}</li>
-            {/each}
-          </ol>
-        </div>
-      {/if}
-    </td>
-  </tr>
-{/if}

+ 0 - 23
web/src/routes/dashboard/contest/_components/ContestTable.svelte

@@ -1,23 +0,0 @@
-<script>
-  import ContestEntry from "./ContestEntry.svelte";
-
-  export let contests = [];
-</script>
-
-<table>
-  <thead>
-    <tr>
-      <th>ID</th>
-      <th>In channel</th>
-      <th>Date started</th>
-      <th>Entry phase duration</th>
-      <th>Vote phase duration</th>
-      <th>Actions</th>
-    </tr>
-  </thead>
-  <tbody>
-    {#each contests as contest, i (contest.id)}
-      <ContestEntry {contest} index={i} />
-    {/each}
-  </tbody>
-</table>

+ 0 - 26
web/src/routes/dashboard/contest/_components/DurationInput.svelte

@@ -1,26 +0,0 @@
-<script>
-  export let id;
-  export let unitId;
-</script>
-
-<style>
-  div {
-    @apply flex;
-  }
-
-  input {
-    @apply flex-grow mr-1;
-  }
-</style>
-
-<div>
-  <input {id} type="number" required />
-  <select id={unitId}>
-    <option value="s">seconds</option>
-    <option value="m">minutes</option>
-    <option value="h">hours</option>
-    <option value="d">days</option>
-    <option value="m">months</option>
-    <option value="y">years</option>
-  </select>
-</div>

+ 0 - 55
web/src/routes/dashboard/contest/_components/EmojiSelector.svelte

@@ -1,55 +0,0 @@
-<script>
-  export let emojis = [
-    ...[...Array(50).keys()].map(i => ({
-      id: `${i}`,
-      url:
-        "https://pbs.twimg.com/profile_images/1094633303196487687/AJ5HL3Tz_400x400.png",
-      name: "asd"
-    }))
-  ];
-  export let name = "";
-
-  let selectedEmoji = "";
-  function emojiSelected(e) {
-    selectedEmoji = e.target.dataset.emojiId;
-  }
-</script>
-
-<style>
-  div.emoji-selector {
-    @apply .flex .flex-wrap .bg-gray-800 .py-1 .px-1 .rounded .border .border-gray-900 w-1/3;
-
-    &:focus {
-      @apply .border-blue-500 .shadow;
-    }
-
-    & > div {
-      @apply .rounded .mr-1 .mb-1;
-
-      & > img {
-        @apply .w-8 .h-auto;
-      }
-
-      &:hover {
-        @apply .bg-gray-500;
-      }
-
-      &[data-selected="true"] {
-        @apply .bg-gray-300;
-      }
-    }
-  }
-</style>
-
-<div class="emoji-selector">
-  <input required type="hidden" {name} value={selectedEmoji} />
-  {#each emojis as emoji (emoji.id)}
-    <div title={emoji.name} data-selected={selectedEmoji === emoji.id}>
-      <img
-        data-emoji-id={emoji.id}
-        on:click={emojiSelected}
-        alt="Emote {emoji.id}"
-        src={emoji.url} />
-    </div>
-  {/each}
-</div>

+ 0 - 63
web/src/routes/dashboard/contest/index.svelte

@@ -1,63 +0,0 @@
-<script>
-  import ContestTable from "./_components/ContestTable.svelte";
-  import AddContestForm from "./_components/AddContestForm.svelte";
-
-  let contests = [
-    {
-      id: 1,
-      channel: "#channel1",
-      started: new Date(),
-      entryDuration: "1d",
-      voteDuration: "1d",
-      winners: ["link1", "link2", "link3"]
-    },
-    {
-      id: 2,
-      channel: "#channel1",
-      started: new Date(),
-      entryDuration: "1d",
-      voteDuration: "1d",
-      winners: ["link1", "link2", "link3"]
-    },
-    {
-      id: 3,
-      channel: "#channel1",
-      started: new Date(),
-      entryDuration: "1d",
-      voteDuration: "1d",
-      winners: ["link1", "link2", "link3"]
-    }
-  ];
-
-  let showAddContest = false;
-
-  function toggleAddContest() {
-    showAddContest = !showAddContest;
-  }
-
-  function contestAdded(newContest) {
-    contests = [newContest, ...contests];
-  }
-</script>
-
-<style lang="css">
-  h2 {
-    @apply .text-xl .font-bold;
-  }
-</style>
-
-<svelte:head>
-  <title>Contest control</title>
-</svelte:head>
-<div class="nav-content">
-  <h2>Actions</h2>
-  <div class="pb-5">
-    <button on:click={toggleAddContest} class="btn">Add contest</button>
-    {#if showAddContest}
-      <AddContestForm on:contestAdded={contestAdded} />
-    {/if}
-  </div>
-
-  <h2>Current contests</h2>
-  <ContestTable {contests}/>
-</div>

+ 0 - 8
web/src/routes/dashboard/index.svelte

@@ -1,8 +0,0 @@
-
-<svelte:head>
-  <title>Dashboard</title>
-</svelte:head>
-
-<div class="content">
-  <h1>Neigh!</h1>
-</div>

+ 9 - 0
web/src/routes/example.ts

@@ -0,0 +1,9 @@
+import { Request as ExpressRequest, Response as ExpressResponse } from "express";
+
+export const get = async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
+	res.end("you made a get request");
+};
+
+export const post = async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
+	res.end("you made a post request");
+};

+ 46 - 14
web/src/routes/index.svelte

@@ -1,14 +1,46 @@
-<script context="module">
-    export async function preload(page, session) {
-        const { user } = session;
-        if(user) return this.redirect(300, "dashboard");
-    }
-</script>
-
-<svelte:head>
-  <title>Loading</title>
-</svelte:head>
-
-<div class="content">
-  <h1>Loading...</h1>
-</div>
+<script lang="typescript">
+	import ExampleComponent from "../components/ExampleComponent.svelte";
+</script>
+
+<style>
+	.centerer {
+		flex: 1 1 0%;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.button {
+		margin-top: 2.5rem;
+		padding: 0.75rem;
+		border-radius: 0.5rem;
+		box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
+		transition-property: background-color, color, box-shadow;
+		transition-duration: 200ms;
+		transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+		text-decoration: none;
+	}
+
+	.button:focus {
+		outline: 0;
+		box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
+	}
+
+	.button-pink {
+		color: #97266D;
+		background-color: #FED7E2;
+	}
+
+	.button-pink:hover, .button-pink:focus {
+		background-color: #FBB6CE;
+	}
+</style>
+
+<div class="centerer">
+	<ExampleComponent
+		title="🌐 Sapper with TypeScript and GraphQL project base"
+		paragraph="This is an example route and component to make sure everything's working." />
+	
+	<a class="button button-pink" href="/graphql">Check out the GraphQL playground!</a>
+</div>

+ 0 - 72
web/src/routes/login/discord/callback.ts

@@ -1,72 +0,0 @@
-import { Request, Response, NextFunction } from "express";
-import request from "request-promise-native";
-import { Response as Res } from "request";
-import { botService } from "src/util/rpc_client";
-
-const API_ENDPOINT = "https://discordapp.com/api";
-
-export async function get(req : Request, res : Response, next: NextFunction) {
-    if(!req.query.code)
-        throw new Error("NoCodeProvided");
-
-    let code = req.query.code;
-    let response = await request("/oauth2/token", {
-        method: "POST",
-        baseUrl: API_ENDPOINT,
-        qs: {
-            grant_type: "authorization_code",
-            code: code,
-            redirect_uri: `${process.env.ADMIN_URL}/login/discord/callback`
-        },
-        auth: {
-            user: process.env.BOT_CLIENT_ID,
-            pass: process.env.BOT_CLIENT_SECRET
-        },
-        resolveWithFullResponse: true
-    }) as Res;
-
-    let authResponse: AuthResponse = JSON.parse(response.body);
-
-    let userInfoResponse = await request("/users/@me", {
-        method: "GET",
-        baseUrl: API_ENDPOINT,
-        auth: {
-            bearer: authResponse.access_token
-        },
-        resolveWithFullResponse: true
-    });
-
-    let discordUser : DiscordUser = JSON.parse(userInfoResponse.body);
-
-    try {
-        let userInfo = await botService.getModeratorUserInfo({id: discordUser.id});
-        req.session.user = userInfo;
-        res.redirect(`${process.env.ADMIN_URL}/`);
-    } catch(e) {
-        console.log(`Failed to authorise user because: ${e}`);
-        res.redirect(`${process.env.ADMIN_URL}/login/?error=invalid_user`);
-        return;
-    }
-};
-
-interface AuthResponse {
-    access_token: string;
-    token_type: string;
-    expires_in: number;
-    refresh_token?: string;
-    scope: string;
-}
-
-interface DiscordUser {
-    id: string;
-    username: string;
-    discriminator: string;
-    avatar?: string;
-    bot?: boolean;
-    mfa_enabled?: boolean;
-    locale?: string;
-    verified?: boolean;
-    email?: string;
-    flags?: number;
-    premium_type?: number;
-}

+ 0 - 10
web/src/routes/login/discord/do.ts

@@ -1,10 +0,0 @@
-import {Request, Response, NextFunction } from "express";
-
-const API_ENDPOINT = "https://discordapp.com/api";
-
-export async function get(req : Request, res : Response, next : NextFunction) {
-    const CALLBACK_URL = encodeURIComponent(`${process.env.ADMIN_URL}/login/discord/callback`);
-    const SCOPE = encodeURIComponent("identify");
-
-    res.redirect(`${API_ENDPOINT}/oauth2/authorize?client_id=${process.env.BOT_CLIENT_ID}&scope=${SCOPE}&response_type=code&redirect_uri=${CALLBACK_URL}`);
-};

+ 0 - 61
web/src/routes/login/index.svelte

@@ -1,61 +0,0 @@
-<script context="module">
-  export async function preload(page, session) {
-    const { host, path, params, query } = page;
-    const { user } = session;
-    if (user) return this.redirect(300, "dashboard");
-    if (query.error) return { errors: query.error.split(",") };
-  }
-</script>
-
-<script>
-  const errorTexts = {
-    invalid_user: "The username is invalid!"
-  };
-
-  export let errors;
-</script>
-
-<style lang="css">
-  .login-box {
-    @apply .flex .flex-col .w-screen .h-screen .justify-center .items-center;
-  }
-
-  .box-item {
-    @apply .w-1/4 .p-2 .rounded;
-  }
-
-  .login-button-list {
-    @apply .flex .justify-center .m-2;
-  }
-
-  .discord-button {
-    @apply .p-2 .bg-indigo-700 .rounded-sm;
-
-    &:hover {
-      @apply .bg-indigo-600;
-    }
-  }
-</style>
-
-<svelte:head>
-  <title>Login</title>
-</svelte:head>
-
-<div class="login-box">
-  {#if errors}
-    {#each errors as errorId}
-      <div class="bg-red-800 box-item mb-2">{errorTexts[errorId]}</div>
-    {/each}
-  {/if}
-  <div class="bg-gray-dark box-item shadow-big text-center">
-    <h1 class="text-4xl">NoctBot Login</h1>
-    <div class="login-button-list">
-      <a class="discord-button" href="/login/discord/do">
-        <span class="icon">
-          <i class="fab fa-discord" />
-        </span>
-        <span>Login with Discord</span>
-      </a>
-    </div>
-  </div>
-</div>

+ 32 - 52
web/src/server.ts

@@ -1,52 +1,32 @@
-import compression from 'compression';
-import * as sapper from '@sapper/server';
-import { createConnection, getConnectionOptions } from "typeorm";
-import express from "express";
-import session from "cookie-session";
-import dotenv from "dotenv";
-import { DB_ENTITIES } from "@shared/db/entities";
-
-if(process.env.NODE_ENV == "development") {
-	console.log(process.cwd());
-    dotenv.config({
-        path: "../.env"
-    });
-    dotenv.config({
-        path: "../db.env"
-    });
-    process.env.TYPEORM_HOST = "localhost";
-    process.env.TYPEORM_USERNAME = process.env.DB_USERNAME;
-    process.env.TYPEORM_PASSWORD = process.env.DB_PASSWORD;
-	process.env.TYPEORM_DATABASE = process.env.DB_NAME;
-}
-
-
-const PORT = +(process.env.PORT as string);
-async function main() {
-	await createConnection({
-		...await getConnectionOptions(),
-		entities: DB_ENTITIES
-	});
-
-	express()
-		.use(
-			session({
-				maxAge: 604800,
-				secret: process.env.ADMIN_COOKIE_KEY,
-				name: "session"
-			}),
-			compression({ threshold: 0 }),
-			express.static("static"),
-			sapper.middleware({
-				session: (req, res) => ({
-					user: req.session && req.session.user
-				})
-			})
-		)
-		.listen(PORT, err => {
-			if (err) console.log('error', err);
-		});
-}
-
-main();
-
+// @ts-ignore -- generated package
+import * as sapper from "@sapper/server"; // eslint-disable-line import/no-unresolved
+import compression from "compression";
+import express, { Express } from "express";
+// @ts-ignore -- doesn't package its own types until 1.0.0-next.6
+import sirv from "sirv";
+
+const PORT = process.env.PORT; // eslint-disable-line prefer-destructuring
+// @ts-ignore -- creates a warning after `rollup-plugin-replace` (set up in `rollup.config.js`)
+// replaces `process.env.NODE_ENV` with `"production"` during `prod`
+const dev = process.env.NODE_ENV === "development";
+
+
+const createSapperServer = async (): Promise<Express> => {
+	const app = express();
+
+	app.use(
+		compression({ threshold: 0 }),
+		sirv("static", { dev }),
+		sapper.middleware(),
+	);
+
+	return app;
+};
+
+createSapperServer().then((app) => {
+	app.listen(PORT, (err?: any): void => { // eslint-disable-line
+		if (err) console.log("error", err);
+	});
+});
+
+export { sapper };

+ 86 - 82
web/src/service-worker.js

@@ -1,82 +1,86 @@
-import { timestamp, files, shell, routes } from '@sapper/service-worker';
-
-const ASSETS = `cache${timestamp}`;
-
-// `shell` is an array of all the files generated by the bundler,
-// `files` is an array of everything in the `static` directory
-const to_cache = shell.concat(files);
-const cached = new Set(to_cache);
-
-self.addEventListener('install', event => {
-	event.waitUntil(
-		caches
-			.open(ASSETS)
-			.then(cache => cache.addAll(to_cache))
-			.then(() => {
-				self.skipWaiting();
-			})
-	);
-});
-
-self.addEventListener('activate', event => {
-	event.waitUntil(
-		caches.keys().then(async keys => {
-			// delete old caches
-			for (const key of keys) {
-				if (key !== ASSETS) await caches.delete(key);
-			}
-
-			self.clients.claim();
-		})
-	);
-});
-
-self.addEventListener('fetch', event => {
-	if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
-
-	const url = new URL(event.request.url);
-
-	// don't try to handle e.g. data: URIs
-	if (!url.protocol.startsWith('http')) return;
-
-	// ignore dev server requests
-	if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
-
-	// always serve static files and bundler-generated assets from cache
-	if (url.host === self.location.host && cached.has(url.pathname)) {
-		event.respondWith(caches.match(event.request));
-		return;
-	}
-
-	// for pages, you might want to serve a shell `service-worker-index.html` file,
-	// which Sapper has generated for you. It's not right for every
-	// app, but if it's right for yours then uncomment this section
-	/*
-	if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
-		event.respondWith(caches.match('/service-worker-index.html'));
-		return;
-	}
-	*/
-
-	if (event.request.cache === 'only-if-cached') return;
-
-	// for everything else, try the network first, falling back to
-	// cache if the user is offline. (If the pages never change, you
-	// might prefer a cache-first approach to a network-first one.)
-	event.respondWith(
-		caches
-			.open(`offline${timestamp}`)
-			.then(async cache => {
-				try {
-					const response = await fetch(event.request);
-					cache.put(event.request, response.clone());
-					return response;
-				} catch(err) {
-					const response = await cache.match(event.request);
-					if (response) return response;
-
-					throw err;
-				}
-			})
-	);
-});
+/* eslint-disable no-restricted-globals,@typescript-eslint/no-explicit-any */
+// @ts-ignore -- generated package
+import { timestamp, files, shell } from "@sapper/service-worker";
+
+const ASSETS = `cache${timestamp}`;
+
+// `shell` is an array of all the files generated by the bundler,
+// `files` is an array of everything in the `static` directory
+const toCache = (shell as string[]).concat(files as string[]);
+const cached = new Set(toCache);
+
+self.addEventListener("install", <EventType extends ExtendableEvent>(event: EventType) => {
+	event.waitUntil(
+		caches
+			.open(ASSETS)
+			.then((cache) => cache.addAll(toCache))
+			.then(() => {
+				(self as any as ServiceWorkerGlobalScope).skipWaiting();
+			}),
+	);
+});
+
+self.addEventListener("activate", <EventType extends ExtendableEvent>(event: EventType) => {
+	event.waitUntil(
+		caches.keys().then(async (keys) => {
+			// delete old caches
+			for (const key of keys) { // eslint-disable-line no-restricted-syntax
+				if (key !== ASSETS) await caches.delete(key); // eslint-disable-line no-await-in-loop
+			}
+
+			(self as any as {clients: Clients}).clients.claim();
+		}),
+	);
+});
+
+self.addEventListener("fetch", <EventType extends FetchEvent>(event: EventType) => {
+	if (event.request.method !== "GET" || event.request.headers.has("range")) return;
+
+	const url = new URL(event.request.url);
+
+	// don't try to handle e.g. data: URIs
+	if (!url.protocol.startsWith("http")) return;
+
+	// ignore dev server requests
+	if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
+
+	// always serve static files and bundler-generated assets from cache
+	if (url.host === self.location.host && cached.has(url.pathname)) {
+		caches.match(event.request).then((match): void => {
+			if (match) event.respondWith(match);
+		});
+		return;
+	}
+
+	// for pages, you might want to serve a shell `service-worker-index.html` file,
+	// which Sapper has generated for you. It's not right for every
+	// app, but if it's right for yours then uncomment this section
+	/*
+	if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
+		event.respondWith(caches.match('/service-worker-index.html'));
+		return;
+	}
+	*/
+
+	if (event.request.cache === "only-if-cached") return;
+
+	// for everything else, try the network first, falling back to
+	// cache if the user is offline. (If the pages never change, you
+	// might prefer a cache-first approach to a network-first one.)
+	event.respondWith(
+		caches
+			.open(`offline${timestamp}`)
+			.then(async (cache) => {
+				try {
+					const response = await fetch(event.request);
+					cache.put(event.request, response.clone());
+					return response;
+				} catch (err) {
+					const response = await cache.match(event.request);
+					if (response) return response;
+
+					throw err;
+				}
+			}),
+	);
+});

+ 0 - 77
web/src/style/main.css

@@ -1,77 +0,0 @@
-@import "tailwindcss/base";
-@import "tailwindcss/components";
-@import "tailwindcss/utilities";
-
-body {
-    @apply .bg-gray-900 .text-white .font-sans;
-}
-
-.shadow-big {
-    box-shadow: 3px 3px 10px -1px rgba(0, 0, 0, 0.75);
-}
-
-.nav-content {
-    & h1 {
-        @apply .text-4xl .font-bold;
-    }
-}
-
-table {
-    @apply .border-gray-900 .rounded-sm .border .w-full;
-
-    & thead tr {
-        @apply .bg-gray-900;
-    }
-
-    th, td {
-        @apply .p-1 .border-gray-900 .border-r .border-b;
-    }
-
-    tbody tr.entry[data-nth="even"] {
-        @apply .bg-gray-800;
-    }
-
-    tbody tr.entry[data-nth="odd"] {
-        @apply .bg-gray-700;
-    }
-}
-
-.btn {
-    @apply .px-2 .py-1 .bg-blue-700 .rounded-sm .font-normal .border .border-blue-700;
-
-    &:hover {
-      @apply .bg-blue-600;
-    }
-
-    &[disabled] {
-      @apply .bg-transparent .border .border-blue-700 .text-gray-400 .cursor-not-allowed;
-    }
-}
-
-.input-ctrl {
-    @apply .bg-gray-800 .py-1 .px-1 .rounded .border .border-gray-900;
-}
-
-.input-ctrl-hover {
-    @apply .border-blue-500 .shadow;
-}
-
-input, select {
-    @apply .input-ctrl;
-
-    &:focus {
-        @apply .input-ctrl-hover;
-    }
-}
-
-form.form-ctrl > div {
-    @apply .flex .py-1 .items-center;
-
-    & > *:nth-child(1) {
-      @apply .flex-grow-0 w-1/6;
-    }
-
-    & > *:nth-child(2) {
-      @apply .flex-grow-0 w-1/3;
-    }
-  }

+ 35 - 35
web/src/template.html

@@ -1,35 +1,35 @@
-<!doctype html>
-<html>
-<head>
-	<meta charset='utf-8'>
-	<meta name='viewport' content='width=device-width,initial-scale=1.0'>
-	<meta name='theme-color' content='#333333'>
-
-	%sapper.base%
-
-	<link rel='stylesheet' href='global.css'>
-	<link rel='manifest' href='manifest.json'>
-	<link rel='icon' type='image/png' href='favicon.png'>
-
-	<!-- Sapper generates a <style> tag containing critical CSS
-	     for the current page. CSS for the rest of the app is
-	     lazily loaded when it precaches secondary pages -->
-	%sapper.styles%
-
-	<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
-
-	<!-- This contains the contents of the <svelte:head> component, if
-	     the current page has one -->
-	%sapper.head%
-</head>
-<body>
-	<!-- The application will be rendered inside this element,
-	     because `app/client.js` references it -->
-	<div id='sapper'>%sapper.html%</div>
-
-	<!-- Sapper creates a <script> tag containing `app/client.js`
-	     and anything else it needs to hydrate the app and
-	     initialise the router -->
-	%sapper.scripts%
-</body>
-</html>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<meta name="viewport" content="width=device-width,initial-scale=1.0" />
+
+		%sapper.base%
+
+		<link rel="manifest" href="manifest.json" />
+		<meta name="theme-color" content="#0096D1">
+
+		<link rel="icon" type="image/png" href="favicon.png" />
+		<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180.png">
+
+		<link rel="stylesheet" href="/main.css" />
+		<link rel="stylesheet" href="/normalize.css" />
+		
+		<!-- Sapper generates a <style> tag containing critical CSS
+  		for the current page. CSS for the rest of the app is
+  		lazily loaded when it precaches secondary pages -->
+		%sapper.styles%
+
+		<!-- This contains the contents of the <svelte:head> component, if
+		the current page has one -->
+		%sapper.head%
+	</head>
+
+	<body>
+		<div id="sapper">
+			%sapper.html%
+		</div>
+
+		%sapper.scripts%
+	</body>
+</html>

+ 0 - 39
web/src/typedefs/sapper.d.ts

@@ -1,39 +0,0 @@
-declare module '@sapper/app' {
-    // from sapper/runtime/src/app/types.ts
-    // sapper doesn't export its types yet
-    interface Redirect {
-        statusCode: number
-        location: string
-    }
-    // end
-
-    function goto(href: string, opts?: { replaceState: boolean }): Promise<unknown>
-    function prefetch(href: string): Promise<{ redirect?: Redirect; data?: unknown }>
-    function prefetchRoutes(pathnames: string[]): Promise<unknown>
-    function start(opts: { target: Node }): Promise<unknown>
-    const stores: () => unknown
-
-    export { goto, prefetch, prefetchRoutes, start, stores }
-}
-
-declare module '@sapper/server' {
-    import { RequestHandler } from 'express'
-
-    interface MiddlewareOptions {
-        session?: (req: Express.Request, res: Express.Response) => unknown
-        ignore?: unknown
-    }
-
-    function middleware(opts?: MiddlewareOptions): RequestHandler
-
-    export { middleware }
-}
-
-declare module '@sapper/service-worker' {
-    const timestamp: number
-    const files: string[]
-    const shell: string[]
-    const routes: { pattern: RegExp }[]
-
-    export { timestamp, files, files as assets, shell, routes }
-}

+ 0 - 6
web/src/util/rpc_client.ts

@@ -1,6 +0,0 @@
-(<any>global).fetch = require("node-fetch");
-import { createClient } from "typescript-rest-rpc/lib/client";
-import { Backend } from "@shared/rpc/backend";
-
-const BOT_ADDRESS = process.env.NODE_ENV == "development" ? "localhost" : "noctbot";
-export const botService : Backend = createClient(`http://${BOT_ADDRESS}:3010/rpc`);

BIN
web/static/apple-touch-icon-180.png


BIN
web/static/favicon.png


BIN
web/static/logo-192.png


BIN
web/static/logo-512.png


+ 17 - 0
web/static/main.css

@@ -0,0 +1,17 @@
+html {
+    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+body {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    min-height: 100vh;
+    line-height: 1.0;
+}
+
+#sapper {
+    display: flex;
+    flex-direction: column;
+    flex: 1 1 0%;
+}

+ 38 - 20
web/static/manifest.json

@@ -1,20 +1,38 @@
-{
-	"background_color": "#ffffff",
-	"theme_color": "#333333",
-	"name": "TODO",
-	"short_name": "TODO",
-	"display": "minimal-ui",
-	"start_url": "/",
-	"icons": [
-		{
-			"src": "logo-192.png",
-			"sizes": "192x192",
-			"type": "image/png"
-		},
-		{
-			"src": "logo-512.png",
-			"sizes": "512x512",
-			"type": "image/png"
-		}
-	]
-}
+{
+	"short_name": "Sapper + TypeScript + GraphQL",
+	"name": "Sapper with TypeScript and GraphQL project base",
+	"description": "A template that includes Svelte with Sapper, TypeScript preprocessing, and a GraphQL server through TypeGraphQL",
+	"categories": ["personalization", "productivity"],
+	"lang": "en-US",
+	"dir": "ltr",
+	"icons": [
+		{
+			"src": "logo-192.png",
+			"sizes": "192x192",
+			"type": "image/png",
+			"purpose": "any maskable"
+		},
+		{
+			"src": "logo-512.png",
+			"sizes": "512x512",
+			"type": "image/png",
+			"purpose": "any maskable"
+		}
+	],
+	"start_url": "/",
+	"display": "minimal-ui",
+	"background_color": "#ffffff",
+	"theme_color": "#0096D1",
+	"screenshots": [
+		{
+			"src": "screenshot-1.png",
+			"sizes": "1280x720",
+			"type": "image/png"
+		},
+		{
+			"src": "screenshot-2.png",
+			"sizes": "1280x720",
+			"type": "image/png"
+		}
+	]
+}

+ 349 - 0
web/static/normalize.css

@@ -0,0 +1,349 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+   ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+  line-height: 1.15; /* 1 */
+  -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+   ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+  margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+  display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+/* Grouping content
+   ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+  box-sizing: content-box; /* 1 */
+  height: 0; /* 1 */
+  overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+   ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+  background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+  border-bottom: none; /* 1 */
+  text-decoration: underline; /* 2 */
+  text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/* Embedded content
+   ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+  border-style: none;
+}
+
+/* Forms
+   ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit; /* 1 */
+  font-size: 100%; /* 1 */
+  line-height: 1.15; /* 1 */
+  margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+  overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+  text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+  border-style: none;
+  padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+  outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+  padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ *    `fieldset` elements in all browsers.
+ */
+
+legend {
+  box-sizing: border-box; /* 1 */
+  color: inherit; /* 2 */
+  display: table; /* 1 */
+  max-width: 100%; /* 1 */
+  padding: 0; /* 3 */
+  white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+  vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+  overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+  box-sizing: border-box; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+  -webkit-appearance: textfield; /* 1 */
+  outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button; /* 1 */
+  font: inherit; /* 2 */
+}
+
+/* Interactive
+   ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+  display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+  display: list-item;
+}
+
+/* Misc
+   ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+  display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+  display: none;
+}

BIN
web/static/screenshot-1.png


BIN
web/static/screenshot-2.png


+ 12 - 0
web/svelte.config.js

@@ -0,0 +1,12 @@
+import sveltePreprocess from "svelte-preprocess";
+
+const dev = process.env.NODE_ENV === "development";
+
+export const preprocess = sveltePreprocess({
+	typescript: {
+		// This returns compilation times back to what they're like without TypeScript
+		// And still type checks for production builds
+		// Use IDE tools for type checking during development instead
+		transpileOnly: dev,
+	},
+});

+ 0 - 11
web/tailwind.config.js

@@ -1,11 +0,0 @@
-module.exports = {
-  theme: {
-    extend: {
-      colors: {
-        'gray-dark': '#0F131A'
-      }
-    },
-  },
-  variants: {},
-  plugins: []
-}

+ 21 - 35
web/tsconfig.json

@@ -1,35 +1,21 @@
-{
-    "compileOnSave": true,
-    "compilerOptions": {
-        "module": "esnext",
-        "noImplicitAny": true,
-        "removeComments": true,
-        "preserveConstEnums": true,
-        "moduleResolution": "node",
-        "outDir": "build",
-        "lib": ["es2018", "dom"],
-        "esModuleInterop": true,
-        "sourceMap": true,
-        "target": "es2018",
-        "emitDecoratorMetadata": true,
-        "experimentalDecorators": true,
-        "baseUrl": ".",
-        "paths": {
-            "@shared/*": ["../shared/src/*"]
-        },
-        "types": [
-            "node-fetch"
-        ]
-    },
-    "references": [
-        {
-            "path": "../shared"
-        }
-    ],
-    "include": [
-        "src/**/*.ts", "src/client.js"
-    ],
-    "exclude": [
-        "node_modules"
-    ]
-}
+{
+    "compilerOptions": {
+        "lib": [
+            "DOM",
+            "WebWorker"
+        ],
+        "target": "ES2016",
+        "allowSyntheticDefaultImports": true,
+        "experimentalDecorators": true,
+        "emitDecoratorMetadata": true,
+        "moduleResolution": "node",
+        "skipLibCheck": true,
+        "strict": true
+    },
+    "include": [
+        "src/**/*"
+    ],
+    "exclude": [
+        "node_modules"
+    ]
+}