merge conflict with master

This commit is contained in:
lenovo
2022-11-28 21:24:22 +01:00
123 changed files with 30360 additions and 4 deletions

40
.gitignore vendored
View File

@@ -14,6 +14,42 @@ Thumbs.db
*.zip
*.log
.env
# compiled output
/dist
node_modules
.idea
./srcs/requirement/api_back/node_modules
./srcs/requirement/api_back/dist
./srcs/requirement/api_front/node_modules
./srcs/requirement/api_front/public/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

View File

@@ -0,0 +1,11 @@
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->
<!DOCTYPE html>
<html>
<head>
<title>Untitled Diagram</title>
<meta charset="utf-8"/>
</head>
<body><div class="mxgraph" style="max-width:100%;border:1px solid transparent;" data-mxgraph="{&quot;highlight&quot;:&quot;#0000ff&quot;,&quot;nav&quot;:true,&quot;resize&quot;:true,&quot;toolbar&quot;:&quot;zoom layers tags lightbox&quot;,&quot;edit&quot;:&quot;_blank&quot;,&quot;xml&quot;:&quot;&lt;mxfile host=\&quot;www.draw.io\&quot; modified=\&quot;2022-10-31T09:17:07.403Z\&quot; agent=\&quot;5.0 (X11; Ubuntu)\&quot; etag=\&quot;c-fjD9Itp8SMr4AJSRT6\&quot; version=\&quot;20.5.1\&quot;&gt;&lt;diagram id=\&quot;6a731a19-8d31-9384-78a2-239565b7b9f0\&quot; name=\&quot;Page-1\&quot;&gt;7V1bc9q6Fv41zN7nIYzvl8eEhLYzTdMpyZzdR4EFuDWIbQuSnF9/JFs2tiyIMVgGah4Sa1nWxfrWRUtLck8fLN4+hWA1f0QeDHqa4r319PuepumaoZN/lPKeUFTVNhPKLPQ9RtsSRv7/ICMqjLr2PRgVMmKEAuyvisQJWi7hBBdoIAzRazHbFAXFWldgBkuE0QQEZep/fQ/P025Y7vbGZ+jP5qxqR7OTG2Mw+T0L0XrJ6luiJUzuLEBaDOtjNAcees2R9IeePggRwsnV4m0AA/pe0zeWPDfccTdrcgiXuNIDtmElz2xAsGYdZ03D7+mbgB55MSwZd0a/i7sHaSkKSf1aL1ZchjleBCSlksuskzRrAMYwuMve0AAFKNw+FWEQ4ls6eBxt6AcBKwEuvTTHJABR5E8SIstCa/wFMX5niAJrjAgJhXiOZmgJgq8IrdKW4RD9hmkbegS1Q/POHmR30nHXCWWKlngIFn5A8fwZBhuI/QlgN1hdqsXS+SJ1wzDvaZErMPGXM0I1Sao8UmzwIrQOJzAbHgY28g5mkGUcPt89gZvI+nHzMtMnd+bzzY11o9oOwxQdrVyJbOA/QbSAOHwnGUIYAOxvijAHjFtmWb7s0e/IJ23UFMbats74mDG24WjFIpK2sqe24CMXuWZsSTEkd8LT6eB5YfAkcl4/NRILGNoPGFcAGCsgbbvz/A25nNHL74j0lJFJmbk7JXRtoUSH5HXuYzgir4refSWa7wgs5Qe3QCyMK4GNCAF1x3sY/wgdBP5sSUFKRhiG6YOs32oJECCcsFJtklrB0CeDA8NRlknbh5oNDDF824sFdteylIJ00W02oK85HWyyPPOc+rWVU8AnLbm2vFFly5vawuTBMS1Dqy9MjD3gKmKHRxaH6bECdWhVFzqpeOGEjtWe0DHVDjUXihqzRdRoHWpaQw188/E/LA+9/km72DdZ6v4tteZo4p0lKiIt1WBNG0Vi81xTOAXqcIrxlOZ52qn91tYL9gM/Ahiuw11GV+mZlDQOecpF2WsZ8I+Gecle2497WfYaMcY4uJl9s5rFZp3GYjOrQHCECU9F2P93DSPqlIIUNPHlAuDJPLoSuE0VBzqqZLgpLcLNTD1pksBW9pb9iMU1WkYdNM4LGporFxr2kdbcVXmqVMt1YxQ2bvAdZZRZLXqqzM61eYGA0doDjCXyTZUsnQFdDnsjCukiDZgMBldrL/Pmsi7XXE4BnAPRPVqPA2oQE4adk575U/KmcYygs8aKZYzHUMbcShY2brJxT5fWTKkmjFVpNv9EYfKnzeOvDmtaq8ayZVRSZXOAGwBaeTWQyD9iy2jD7yEhDvTerUsTIKTuAW91JRAeKo6tmBIg3JZqNbjABNWRi+myI+p24VMXEy1EU8YB+nedoOvsfQUmmJLpyRVJO843oEoWd12QlUgQnc1UzxBO9VpcGrREzqSS3nqEUQRmsRd7q7miWNocpPmii9RnVyekeP1lyZVRnTvqAmWU3aKMKkfaDcJEBjHvQbLMNpmD5ZIGh3fSpE1pInmGZ4uclZ04aVycpHEt21iWn71cKIswroWALHzPPUSTP1lX4sT2sTh1aDyMMEg4FWhNx8MYTtGnVsL36eJhdgTm22VBKTLmaBzCI/jtd5HJjUUml6SnAL2VI5O1qs7ZU0Qmf7DnYz+0vkLgwXCMQOh10DpHaJkOF9WingG0jo1pvioFLm/njViBp2Gqhynwrcrep8A5DfyhRv9AzRXVfLIf5+RaPYv7SlfKVKNYRNLgklYvF6RyBWmyzYN0+1wXBn42YeD2/jjwLbsVmI09dnJ+27V3oSHW0rkAcsO00xCFQ5mLL8rU7b6b+zmyWU207PjH6jR5IVd1NlvsYrKmeCwNjKmy09pshPFUXhWZZt/YzS1VmVC1i8VahnQNVylE/xNCs0IY0gRggqtLnLCcY7TIURMULlrEkLk0sQtU3Zrq2cjyqrOT85PkdiOS/EZX+9yM3lH3WT4fyvL2ZHellejR46gT1GcoqKVuj9qFINHCcglBD8+k+KeXiigiLwhzUBGNf360Gak0TvR1k6EObtmNhe95tBohNova4wTjxdlmIsefJRguvcHhqrRa8dmPiHFGt1P2ut2UbW6ZOwp9usvB7wzEhSta0ujcYZLcYTUdyXWc1nXNO5EzbK8suygPmSbbJ+Z27ufz47dqkRenciRbDbGJbfQV21Cyn13kGkXru7ZWj2/49R/N1GTzjWiz1Hnwzda7kGedo/wL18M6dQKkarObJWS3ZtzHuuYWWcJVavIWFzmoW3t9F81zWrdq024oYZ5PKq/ayIhE2AZAS+EwIz3TOuUwTa9t99l2X8n9DC5YV7P6Zu62KpvlRCs2Hcud90KpWmC45tzrwp0IkuJ8VKfmKqjDedoM3oW2g6cIqMB7LtuKZoh2N9jROcNUVfa3i8ufBjJtWTppwakZXLR6VvLy3a6pS480mLr21ss44WUev1+Q3s4ct4rHPICr+IQ1fzpNNoNQOMHL3GJ29YGnNhccbyqCw0hsgQOQP1TylA7A7iCtdgJPj/b92c07/w74HkIi4k6ukWyDj4XgiqiqkfiCTLXd4Di30jrdY6IARis4wXtPdO0Ee5uC3eF3PVU9C6PBHQWuaGXxPDxmf5KnWc4s4YBVmkTwNOB+3jvFNgy17uTdSvfKZ0VpfFFNS2tHOXaZtLOSjrOSuNn2h7H/8pZIP9DvUubtrslxm1LTGe3yBz63ayU5qSuu47v7VsJOz9oZLV5d1ZpRb0VftGFqfduqx2Oqxs1oiDgzZPNVpbCzlLQunfpX6RjAUi5CKxVV+aTB3Y14ef7y9cvo9vnh5cehtVc9vnAd8JTAzyjm3VuP6C5NKXx5g0D7lvydAj+kU7gA9Oi3KiN6vUJxhtjFtxZ+rSNXet364ijB9wjHbkFnkfkRQ8jOxbuN/YQKiE/Nq1ozrWdn3QSXOK4c0p6tAjrh3NOSCBLxGJ9c8/cGJa1O6GSqu8ZxqcP/yG/aX2ADMDhgNOpUTNHg7T4JWHqf+WNmG+ryHBB1O8tGeoFwerkiupL+/xts4IT8jxvUwujnAdhW3Q3z41/zYjhxFkkM91dMBWhODnJmWI0g8cLUvWB/sNRzbAvd0P4InVhoA8NpEBtbc9/zSDcbCBpXFe6caMF2LCOd7uadO9m3lU/v3XFU0YT0UhX44Onbt4d/vjx9O03dHyjrjGVyZ/fLFdaVKz6pqD6ouycV1E0Iju1c6HIEh8gtLJQcfCxZBcFBktsPmScThe2X4vWH/wM=&lt;/diagram&gt;&lt;/mxfile&gt;&quot;}"></div>
<script type="text/javascript" src="https://viewer.diagrams.net/js/viewer-static.min.js"></script>
</body>
</html>

44
Makefile Normal file
View File

@@ -0,0 +1,44 @@
DOCKERCOMPOSEPATH=./srcs/docker-compose.yml
#dev allow hot reload.
dev:
docker compose -f ${DOCKERCOMPOSEPATH} up -d --build
@make start
@docker ps
#prod only the needed files ares presents inside the container
prod:
docker compose -f ${DOCKERCOMPOSEPATH} up -d --build prod
@make start_prod
@docker ps
start:
docker compose -f ${DOCKERCOMPOSEPATH} start
docker logs --follow srcs-backend_dev-1
start_dev:
docker compose -f ${DOCKERCOMPOSEPATH} start dev
docker logs --follow srcs-backend_dev-1
start_prod:
docker compose -f ${DOCKERCOMPOSEPATH} start prod
restart:stop
@make up
down:
docker compose -f ${DOCKERCOMPOSEPATH} -v down
destroy:
# rm -rf ./srcs/requirements/nestjs/api_back/node_modules/
# rm -rf ./srcs/requirements/nestjs/api_back/dist
# rm -rf ./srcs/requirements/svelte/api_front/node_modules/
# rm -rf ./srcs/requirements/svelte/api_front/public/build
docker compose -f ${DOCKERCOMPOSEPATH} down -v --rmi all --remove-orphans
docker ps -aq | xargs --no-run-if-empty docker rm -f
docker images -aq | xargs --no-run-if-empty docker rmi -f
docker volume ls -q | xargs --no-run-if-empty docker volume rm
stop:
docker compose -f ${DOCKERCOMPOSEPATH} stop

View File

@@ -1,6 +1,30 @@
### hugo chat :
### Pour lancer le docker :
- Il faut un fichier .env qu'on ne doit pas push, donc je ne le push pas.
- Pour l'instant, on doit donc le faire à la main (je verrai par la suite comment faire mieux).
- Dans le .env il y a juste à mettre (sans les guillemets) "NODE_ENV=development" ou "NODE_ENV=production".
- Il faut le placer au même endroit que docker-compose.yml
- Dans le makefile il y a un sedf pour changer l'un ou l'autre.
### TODO List : Utilisateur édition.
- [x] Utilisateur : faire la base pour un utilisateur
- [x] Utilisateur : faire le système de requêtes amis
- [ ] Utilisateur : mettre en place le système de session (voire de statut ?)
- [ ] Utilisateur : mettre en place le système d'avatar
- [ ] Utilisateur : mettre en place la double authentification
- [ ] Utilisateur : mettre en place le système d'Oauth
- [ ] Utilisateur : mettre en place la hashage de mot de passe (avec Oauth)
- [ ] Utilisateur : mettre en place le système de statut
- [ ] Utilisateur : mettre en place le système de stats
- [ ] Utilisateur : mettre en place l'historique des matches
### TODO List : Docker édition.
- [ ] Docker : trouver un moyen simple de générer un .env. Peut-être renouveller les clé à chaque lancement.
- [ ]
---
@@ -59,3 +83,31 @@
- [ ] reponsive
- [ ] can watch other matchs
---
## Resources
- [routes back](https://semestriel.framapad.org/p/z5gqbq51dx-9xlo?lang=fr)
### Svelte
- [The Official Svelte Tutorial](https://svelte.dev/tutorial/basics)
- SPA Svelte Article [Build a single-page application in Svelte with svelte-spa-router](https://blog.logrocket.com/build-spa-svelte-svelte-spa-router/)
- [An excellent Svelt Tutorial video series](https://www.youtube.com/watch?v=zojEMeQGGHs&list=PL4cUxeGkcC9hlbrVO_2QFVqVPhlZmz7tO&index=2)
### nestjs
- [linkedin clone angular nestjs](https://www.youtube.com/watch?v=gL3D-MIt_G8&list=PL9_OU-1M9E_ut3NA04C4eHZuuAFQOUwT0&index=1)
- [nestjs crash course](https://www.youtube.com/watch?v=vGafqCNCCSs)
### websocket
- [game networking](https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/)
- [client-server game architecture](https://www.gabrielgambetta.com/client-server-game-architecture.html)
- [websocket api mozilla doc](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
- [websocket rfc](https://www.rfc-editor.org/rfc/rfc6455.html)
- [ws doc npm](https://www.npmjs.com/package/ws)
- [exemple chat implementation](https://github.com/mdn/samples-server/tree/master/s/websocket-chat)
### css
- [separation of concern](https://adamwathan.me/css-utility-classes-and-separation-of-concerns/)
- [decoupling css and html](https://www.smashingmagazine.com/2012/04/decoupling-html-from-css/)

9
srcs/.env Normal file
View File

@@ -0,0 +1,9 @@
NODE_ENV=development
POSTGRES_USER=postgres
POSTGRES_PASSWORD=9pKpKEgiamxwk5P7Ggsz
POSTGRES_DB=transcendance_db
POSTGRES_HOST=postgresql
POSTGRES_PORT=5432
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=1a5e04138b91b3d683c708e4689454c2

77
srcs/docker-compose.yml Normal file
View File

@@ -0,0 +1,77 @@
services:
backend_dev:
build:
context: ./requirements/nestjs
target: development
dockerfile: Dockerfile
volumes:
- ./requirements/nestjs/api_back/src:/usr/app/src
- ./requirements/nestjs/api_back/test:/usr/app/test/
env_file:
- .env
environment:
NODE_ENV: "${NODE_ENV}"
restart: unless-stopped
depends_on:
- postgresql
- redis
frontend_dev:
build:
context: ./requirements/svelte
target: development
dockerfile: Dockerfile
volumes:
- ./requirements/svelte/api_front/src:/usr/app/src/
- ./requirements/svelte/api_front/public:/usr/app/public/
env_file:
- .env
environment:
NODE_ENV: "${NODE_ENV}"
restart: unless-stopped
depends_on:
- postgresql
- redis
- backend_dev
# t'embete pas a gerer ton propre container nginx
nginx:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./requirements/nginx/conf/default.conf:/etc/nginx/conf.d/default.conf:ro
command: [nginx-debug, "-g", "daemon off;"]
ports:
- "8080:8080"
depends_on:
- backend_dev
- frontend_dev
- postgresql
- redis
postgresql:
container_name: nestjs_postgresql
image: postgres
volumes:
- data_nest_postgresql:/var/lib/postgresql/data
restart: unless-stopped
environment:
POSTGRES_USER: "${POSTGRES_USER}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_DB: "${POSTGRES_DB}"
POSTGRES_HOST: "${POSTGRES_HOST}"
POSTGRES_PORT: "${POSTGRES_PORT}"
# Je connais pas redis, mais si t'en a besoin que a l'interieur de tes containers, je pense pas que t'as besoin d'un expose.
redis:
container_name: nestjs_redis
image: redis:alpine
expose:
- "6379"
restart: unless-stopped
environment:
REDIS_HOST: "${REDIS_HOST}"
REDIS_PORT: "${REDIS_PORT}"
volumes:
data_nest_postgresql:

View File

@@ -0,0 +1,12 @@
# Ignore everything
*
# Allow files and folders with a pattern starting with !
!api_back/*.js
!api_back/*.ts
!api_back/*.json
!api_back/*.html
!api_back/*.lock
!api_back/.env
!api_back/src/uploads/avatars/default.png

View File

@@ -0,0 +1,12 @@
FROM node:alpine AS development
WORKDIR /usr/app
COPY ./api_back ./
COPY ./api_back/.env ./.env
COPY ./api_back/src/uploads/avatars/default.png ./uploads/avatars/default.png
RUN npm ci
CMD [ "npm", "run", "start:dev" ]

View File

@@ -0,0 +1,22 @@
NODE_ENV=development
POSTGRES_HOST=postgresql
POSTGRES_PORT=5432
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=9pKpKEgiamxwk5P7Ggsz
POSTGRES_DATABASE=transcendance_db
# OAUTH2 42 API
FORTYTWO_CLIENT_ID=u-s4t2ud-49dc7b539bcfe1acb48b928b2b281671c99fc5bfab1faca57a536ab7e0075500
FORTYTWO_CLIENT_SECRET=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
FORTYTWO_CALLBACK_URL=http://transcendance:8080/api/v2/auth/redirect
COOKIE_SECRET=248cdc831110eec8796d7c1edbf79835
# JWT
JWT_SECRET=442d774798979fcc14a4a2b6b7535902
# Misc
PORT=3000
#Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=1a5e04138b91b3d683c708e4689454c2
#2fa
TWO_FACTOR_AUTHENTICATION_APP_NAME=Transcendance

View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir : __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View File

@@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
{
"name": "api_back",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/jwt": "^9.0.0",
"@nestjs/mapped-types": "^1.2.0",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/typeorm": "^9.0.1",
"@types/express-session": "^1.17.5",
"@types/redis": "^4.0.11",
"@types/validator": "^13.7.9",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"connect-redis": "^6.1.3",
"express": "^4.18.2",
"express-session": "^1.17.3",
"nest-router": "^1.0.9",
"otplib": "^12.0.1",
"passport": "^0.6.0",
"passport-42": "^1.2.6",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"pg": "^8.8.0",
"qrcode": "^1.5.1",
"redis": "^4.4.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.3.10",
"uuid": "^9.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/passport-jwt": "^3.0.7",
"@types/passport-local": "^1.0.34",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.3",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

View File

@@ -0,0 +1,33 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { FriendshipsModule } from './friendship/friendships.module';
import { AuthenticationModule } from './auth/42/authentication.module';
import { PassportModule } from '@nestjs/passport';
@Module({
imports: [UsersModule,
AuthenticationModule,
PassportModule.register({ session: true }),
FriendshipsModule,
ConfigModule.forRoot(),
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.POSTGRES_HOST,
port: parseInt(process.env.POSTGRES_PORT),
username: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DATABASE,
autoLoadEntities: true,
//ne pas synchroniser quand on est en prod. Trouver un moyen de set ça, sûrement
//avec une classe pour le module
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello Toto!';
}
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthenticationController } from './authentication.controller';
describe('AuthenticationController', () => {
let controller: AuthenticationController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthenticationController],
}).compile();
controller = module.get<AuthenticationController>(AuthenticationController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,79 @@
import { Controller, Get, Res, UseGuards, Req, Post, UnauthorizedException, Body, Options, Next } from '@nestjs/common';
import { AuthenticateGuard, FortyTwoAuthGuard, TwoFactorGuard } from './guards/42guards';
import { AuthenticationService } from './authentication.service';
import { Response } from 'express';
import { TwoFaDto } from './dto/2fa.dto';
import { UsersService } from 'src/users/users.service';
@Controller('auth')
export class AuthenticationController {
constructor(private authService: AuthenticationService,
private userService: UsersService,
) {}
/**
* GET /api/v2/auth
* Route pour l'autentification des utilisateurs
*/
@Get()
@UseGuards(FortyTwoAuthGuard)
login(@Res() response : Response) {
console.log('ON EST DANS LOGIN AUTH CONTROLLER');
return ;
}
/**
* GET /api/v2/auth/redirect
* C'est la route que nous devons spécifier à l'Oauth de 42.
* L'api de 42 redirige vers cette route après l'autentification.
*/
@Get('redirect')
@UseGuards(FortyTwoAuthGuard)
async redirect(@Res() response : Response, @Req() request) {
console.log('ON EST DANS REDIRECT AUTH CONTROLLER');
console.log('On redirige');
if (request.user.isEnabledTwoFactorAuth === false)
return response.status(200).redirect('http://transcendance:8080');
return response.status(200).redirect('http://transcendance:8080/#/2fa');
}
/**
* GET /api/v2/auth/logout
* Route pour déconnecter l'utilisateur
*/
@Post('logout')
@UseGuards(AuthenticateGuard)
logout(@Req() request, @Res() response, @Next() next) {
this.userService.setIsTwoFactorAuthenticatedWhenLogout(request.user.id);
this.userService.updateStatus(request.user.id, 'disconnected');
request.logout(function(err) {
if (err) { return next(err); }
response.redirect('/');
});
request.session.cookie.maxAge = 0;
return {msg : 'You are now logged out'};
}
@Post('2fa/generate')
@UseGuards(AuthenticateGuard)
async register(@Req() request, @Res() response){
console.log('ON EST DANS REGISTER POUR 2FA AUTH CONTROLLER')
const { otpauth } = await this.authService.generate2FaSecret(request.user);
return this.authService.pipeQrCodeStream(response, otpauth);
}
@Post('2fa/turn-on')
@UseGuards(AuthenticateGuard)
async verify(@Req() request, @Body() {twoFaCode} : TwoFaDto, @Res() response){
console.log('ON EST DANS VERIFY POUR 2FA AUTH CONTROLLER')
const isCodeIsValid = await this.authService.verify2FaCode(request.user, twoFaCode);
if (isCodeIsValid === false)
{
throw new UnauthorizedException('Wrong Code.');
}
await this.userService.enableTwoFactorAuth(request.user.id);
console.log('ON REDIRIGE');
return response.status(200);
}
}

View File

@@ -0,0 +1,30 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Friendship } from 'src/friendship/entities/friendship.entity';
import { FriendshipService } from 'src/friendship/friendship.service';
import { User } from 'src/users/entities/user.entity';
import { UsersModule } from 'src/users/users.module';
import { UsersService } from 'src/users/users.service';
import { AuthenticationController } from './authentication.controller';
import { AuthenticationService } from './authentication.service';
import { FortyTwoStrategy } from './strategy/42strategy';
import { SessionSerializer } from './utils/serializer';
@Module({
imports: [TypeOrmModule.forFeature([User, Friendship]), UsersModule,
// JwtModule.registerAsync({
// useFactory: async (configService: ConfigService) => {
// return {
// signOptions: { expiresIn: '1h' },
// secret: process.env.JWT_SECRET,
// };
// }
// })
],
providers: [AuthenticationService, FortyTwoStrategy, UsersService, SessionSerializer, FriendshipService
// JwtStrategy
],
exports: [AuthenticationService],
controllers: [AuthenticationController],
})
export class AuthenticationModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthenticationService } from './authentication.service';
describe('AuthenticationService', () => {
let service: AuthenticationService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthenticationService],
}).compile();
service = module.get<AuthenticationService>(AuthenticationService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,56 @@
import { Injectable } from '@nestjs/common';
import { CreateUsersDto } from 'src/users/dto/create-users.dto';
import { User } from 'src/users/entities/user.entity';
import { UsersService } from 'src/users/users.service';
import { toFileStream } from 'qrcode';
import { authenticator } from 'otplib';
import { randomUUID } from 'crypto';
@Injectable()
export class AuthenticationService {
constructor(
private readonly userService: UsersService,
) {}
async validateUser(createUsersDto :CreateUsersDto){
console.log("Validate inside authentication.service.ts");
const user = await this.userService.findOneByFourtyTwoId(createUsersDto.fortyTwoId);
if (user)
return user;
let check_name : boolean = false;
if (!user)
check_name = await this.userService.isUsernameExists(createUsersDto.username);
if (!check_name)
return await this.userService.create(createUsersDto);
let createUsersDtoWithUsername : CreateUsersDto = createUsersDto;
while (check_name === true)
{
createUsersDtoWithUsername = { ...createUsersDto, username: createUsersDto.username + randomUUID() };
check_name = await this.userService.isUsernameExists(createUsersDtoWithUsername.username);
}
return this.userService.create(createUsersDtoWithUsername);
}
async findUser(fourtytwo_id : string): Promise<User | undefined> {
return await this.userService.findOneByFourtyTwoId(fourtytwo_id);
}
async verify2FaCode(user : User, code : string) {
return authenticator.verify({ token: code, secret: user.secretTwoFactorAuth });
}
async generate2FaSecret(user : User) {
const secret = authenticator.generateSecret();
const otpauth = authenticator.keyuri(user.email, process.env.TWO_FACTOR_AUTHENTICATION_APP_NAME, secret);
await this.userService.setAuthenticatorSecret(user.id, secret);
return { secret, otpauth };
}
async pipeQrCodeStream(stream : Response, otpauthUrl : string) {
return toFileStream(stream, otpauthUrl);
}
}

View File

@@ -0,0 +1,6 @@
import { IsEmpty, IsString } from "class-validator";
export class TwoFaDto {
@IsString()
readonly twoFaCode: string;
}

View File

@@ -0,0 +1,32 @@
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class FortyTwoAuthGuard extends AuthGuard('42') {
async canActivate(context: ExecutionContext): Promise<any> {
const activate = (await super.canActivate(context)) as boolean;
const request = context.switchToHttp().getRequest();
await super.logIn(request);
return activate;
}
}
@Injectable()
export class AuthenticateGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
console.log("AuthenticateGuard : Is User authenticated : " + request.isAuthenticated());
return request.isAuthenticated();
}
}
@Injectable()
export class TwoFactorGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
//console.log("TwoFactorGuard : Is User authenticated : " + request.isAuthenticated() + " and has 2FA enabled : " + request.user.isEnabledTwoFactorAuth + " and has 2FA verified : " + request.user.isTwoFactorAuthenticated);
return request.isAuthenticated() && (request.user.isEnabledTwoFactorAuth === false
|| request.user.isTwoFactorAuthenticated === true);
}
}

View File

@@ -0,0 +1,27 @@
import { Strategy, Profile } from "passport-42/lib";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { AuthenticationService } from "../authentication.service";
import { CreateUsersDto } from "src/users/dto/create-users.dto";
@Injectable()
export class FortyTwoStrategy extends PassportStrategy(Strategy, "42") {
constructor(private authenticationService: AuthenticationService) {
super({
clientID: process.env.FORTYTWO_CLIENT_ID,
clientSecret: process.env.FORTYTWO_CLIENT_SECRET,
callbackURL: process.env.FORTYTWO_CALLBACK_URL,
scope: ["public"],
});
}
async validate(accessToken: string, refreshToken: string, profile: Profile, callbackURL: string) {
console.log("Validate inside strategy.ts");
console.log(profile.id, profile.username, profile.phoneNumbers[0].value, profile.emails[0].value, profile.photos[0].value);
const userDTO: CreateUsersDto = { fortyTwoId: profile.id, username: profile.username, email: profile.emails[0].value, image_url: 'default.png', isEnabledTwoFactorAuth: false , status: "connected" };
const user = await this.authenticationService.validateUser(userDTO);
if (!user)
throw new UnauthorizedException();
return user;
}
}

View File

@@ -0,0 +1,23 @@
import { PassportSerializer } from "@nestjs/passport";
import { Injectable } from "@nestjs/common";
import { User } from "src/users/entities/user.entity";
import { AuthenticationService } from "../authentication.service";
@Injectable()
export class SessionSerializer extends PassportSerializer {
constructor(private readonly authservice : AuthenticationService) {
super();
}
serializeUser(user : User, done : (err : Error, user : User) => void ){
done(null, user);
}
async deserializeUser(user : User, done : (err : Error, user : User) => void){
const userDB = await this.authservice.findUser(user.fortyTwoId);
if (userDB)
done(null, userDB);
else
done(null, null);
}
}

View File

@@ -0,0 +1,22 @@
import { randomUUID } from "crypto";
import { diskStorage } from "multer";
const MIME_TYPES = {
'image/jpg': 'jpg',
'image/jpeg': 'jpg',
'image/png': 'png'
};
export const storageForAvatar = {
storage: diskStorage({
destination: './uploads/avatars',
filename: (req, file, cb) => {
const filename : string = file.originalname.split(' ').join('_') + randomUUID();
console.log("RES : " )
const extension : string = MIME_TYPES[file.mimetype];
cb(null, `${filename}${extension}`);
}
})
}

View File

@@ -0,0 +1,12 @@
import { IsOptional, IsPositive } from "class-validator";
export class PaginationQueryDto {
@IsOptional()
@IsPositive()
limit: number;
@IsPositive()
@IsOptional()
offset: number;
}

View File

@@ -0,0 +1,24 @@
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToInstance(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}

View File

@@ -0,0 +1,11 @@
import { IsEnum, IsString } from 'class-validator';
import { FriendshipStatus } from '../entities/friendship.entity';
export class CreateFriendshipDto {
@IsString()
readonly requesterId: string;
@IsString()
readonly addresseeId: string;
@IsEnum(FriendshipStatus)
readonly status: FriendshipStatus;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from "@nestjs/mapped-types";
import { CreateFriendshipDto } from "./create-friendship.dto";
export class UpdateFriendshipDto extends PartialType(CreateFriendshipDto){}

View File

@@ -0,0 +1,30 @@
import { IsOptional } from "class-validator";
import { Column, CreateDateColumn, Entity, JoinTable, ManyToOne, OneToMany, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import { User } from "../../users/entities/user.entity";
export enum FriendshipStatus {
REQUESTED = 'R',
ACCEPTED = 'A',
DECLINED = 'D',
BLOCKED = 'B',
}
@Entity('friendships')
export class Friendship {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn()
date : Date;
@Column()
@ManyToOne(type => User, user => user.requesterId)
requesterId: string;
@Column()
@ManyToOne(type => User, user => user.addresseeId)
addresseeId: string;
@Column({ type: 'enum', enum: FriendshipStatus, default: FriendshipStatus.REQUESTED})
status: FriendshipStatus;
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FriendshipController } from './friendship.controller';
describe('FriendshipController', () => {
let controller: FriendshipController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [FriendshipController],
}).compile();
controller = module.get<FriendshipController>(FriendshipController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,87 @@
import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Patch, Post, Query, Req, UseGuards } from '@nestjs/common';
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
import { User } from 'src/users/entities/user.entity';
import { CreateFriendshipDto } from './dto/create-friendship.dto';
import { FriendshipService } from './friendship.service';
@Controller('network')
export class FriendshipController {
constructor(private readonly friendshipService: FriendshipService) { }
// GET http://transcendance:8080/api/v2/network/myfriends
@Get('myfriends')
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
findEmpty(@Req() req) {
const user = req.user;
return this.friendshipService.findAllFriends(user.id);
}
// GET http://transcendance:8080/api/v2/network/myfriends/relationshipId
@Get('myfriends/:relationshipId')
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
findOneFriend(@Param('relationshipId') relationshipId: string, @Req() req) {
const user = req.user;
return this.friendshipService.findOneFriend(relationshipId, user.id);
}
// POST http://transcendance:8080/api/v2/network/myfriends
@Post('myfriends')
@HttpCode(HttpStatus.CREATED)
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
create(@Body() createFriendshipDto: CreateFriendshipDto, @Req() req) {
const user = req.user;
console.log(`User id: ${user.id}\nFriend id: ${createFriendshipDto.requesterId}\nStatus: ${createFriendshipDto.status}`);
if (user.id === +createFriendshipDto.requesterId)
return this.friendshipService.create(createFriendshipDto, user);
return new HttpException('You can\'t request a frienship for another user', HttpStatus.FORBIDDEN);
}
// PATCH http://transcendance:8080/api/v2/network/myfriends/relationshipId?status=A
@Patch('myfriends/:relationshipId')
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
update(@Param('relationshipId') relationshipId: string, @Query('status') status : string, @Req() req)
{
const user : User = req.user;
return this.friendshipService.updateFriendship(relationshipId, user, status);
}
// DELETE http://transcendance:8080/api/v2/network/myfriends/relationshipId
@Delete('myfriends/:relationshipId')
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
remove(@Param('relationshipId') relationshipId: string) {
return this.friendshipService.removeFriendship(relationshipId);
}
// GET http://transcendance:8080/api/v2/network/blocked
@Get('blocked')
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
findAllBlocked(@Req() req) {
const user = req.user;
return this.friendshipService.findAllBlockedFriends(user.id);
}
// GET http://transcendance:8080/api/v2/network/pending
@Get('pending')
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
findAllPendantFriendshipRequested(@Req() req) {
const user = req.user;
return this.friendshipService.findAllPendantRequestsForFriendship(user.id);
}
// GET http://transcendance:8080/api/v2/network/received
@Get('received')
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
findAllPendantFriendshipReceived(@Req() req) {
const user = req.user;
return this.friendshipService.findAllReceivedRequestsForFriendship(user.id);
}
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FriendshipService } from './friendship.service';
describe('FriendshipService', () => {
let service: FriendshipService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [FriendshipService],
}).compile();
service = module.get<FriendshipService>(FriendshipService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,133 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from 'src/users/entities/user.entity';
import { Repository } from 'typeorm';
import { CreateFriendshipDto } from './dto/create-friendship.dto';
import { UpdateFriendshipDto } from './dto/update-friendship.dto';
import { Friendship, FriendshipStatus } from './entities/friendship.entity';
@Injectable()
export class FriendshipService {
constructor(
@InjectRepository(Friendship)
private readonly friendshipRepository: Repository<Friendship>,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) { }
async findOneFriend(friendshipId: string, userId: string) {
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, requesterId: userId, status: FriendshipStatus.ACCEPTED } });
if (!friendship)
throw new HttpException(`The requested friend not found.`, HttpStatus.NOT_FOUND);
return friendship;
}
async findOneBlocked(friendshipId: string) {
const friendship = await this.friendshipRepository.find({ where: { id: +friendshipId, status: FriendshipStatus.BLOCKED } });
if (!friendship)
throw new HttpException(`The requested user not found.`, HttpStatus.NOT_FOUND);
return friendship;
}
async findAllFriends(userId: string) {
const friendship = await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
.andWhere('friendship.addresseeId = :addressee', { addressee: userId })
.orWhere('friendship.requesterId = :requester', { requester: userId })
.andWhere('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
.getMany();
for (const friend of friendship)
console.log("FRIENDSHIP : " + friend.status);
return friendship;
}
async findAllBlockedFriends(userId: string) {
return await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.requesterId = :requestee', { requestee: userId })
.orWhere('friendship.addresseeId = :addressee', { addressee: userId })
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
.getMany();
}
async findAllPendantRequestsForFriendship(userId: string) {
return await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.requesterId = :requestee', { requestee: userId })
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
.getMany();
}
async findAllReceivedRequestsForFriendship(userId: string) {
return await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.addresseeId = :addressee', { addressee: userId })
.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
.getMany();
}
//GROS CHANTIER
async create(createFriendshipDto: CreateFriendshipDto, creator : User) {
const addressee = await this.userRepository.findOneBy({ id: +createFriendshipDto.addresseeId });
if (!addressee)
throw new HttpException(`The addressee does not exist.`, HttpStatus.NOT_FOUND);
if (creator.id === addressee.id)
throw new HttpException(`You can't add yourself.`, HttpStatus.FORBIDDEN);
if (createFriendshipDto.status !== FriendshipStatus.REQUESTED && createFriendshipDto.status !== FriendshipStatus.BLOCKED)
throw new HttpException(`The status is not valid.`, HttpStatus.NOT_FOUND);
const friendship = await this.friendshipRepository.findOneBy({ requesterId: createFriendshipDto.requesterId, addresseeId: createFriendshipDto.addresseeId });
if (friendship) {
if (friendship.status && friendship.status === FriendshipStatus.ACCEPTED)
throw new HttpException(`The friendship request has already been accepted.`, HttpStatus.OK);
else if (friendship.status && friendship.status === FriendshipStatus.REQUESTED)
throw new HttpException(`The friendship request has already been sent the ${friendship.date}.`, HttpStatus.OK);
else if (friendship.status && friendship.status === FriendshipStatus.BLOCKED)
throw new HttpException(`We can't do that`, HttpStatus.OK);
else if (friendship.status && friendship.status === FriendshipStatus.DECLINED)
throw new HttpException(`The request has been declined.`, HttpStatus.OK);
}
const newFriendship = this.friendshipRepository.create(createFriendshipDto);
return this.friendshipRepository.save(newFriendship);
}
async updateFriendship(relationshipId: string, user: User, status: string) {
const relation = await this.friendshipRepository.findOneBy({ id: +relationshipId });
if (!relation)
throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
if (+relation.requesterId === user.id) {
throw new HttpException(`You can't accept your own request.`, HttpStatus.NOT_FOUND);
}
if (status === FriendshipStatus.ACCEPTED)
relation.status = FriendshipStatus.ACCEPTED;
else if (status === FriendshipStatus.DECLINED)
relation.status = FriendshipStatus.DECLINED;
else if (status === FriendshipStatus.BLOCKED)
relation.status = FriendshipStatus.BLOCKED;
else
throw new HttpException(`The status is not valid.`, HttpStatus.NOT_FOUND);
if (relation.status !== status)
throw new HttpException(`We could not update the status.`, HttpStatus.OK);
return this.friendshipRepository.save(relation);
}
async removeFriendship(relationshipId: string) {
const friendship = await this.friendshipRepository.findOneBy({ id: +relationshipId });
if (!friendship)
throw new HttpException(`Your friend could not be deleted.`, HttpStatus.NOT_FOUND);
return this.friendshipRepository.remove(friendship);
}
async findIfUserIsBlockedOrHasBlocked(userConnectedId: string, userToCheckId: string) {
const friendship = await this.friendshipRepository
.createQueryBuilder('friendship')
.where('friendship.requesterId = :requestee', { requestee: userConnectedId })
.orWhere('friendship.requesterId = :requesteeBis', { requesteeBis: userToCheckId })
.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
.getOne();
if (friendship)
return true;
return false;
}
}

View File

@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FriendshipService } from './friendship.service';
import { FriendshipController } from './friendship.controller';
import { Friendship } from './entities/friendship.entity';
import { User } from '../users/entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([Friendship, User])],
providers: [FriendshipService],
controllers: [FriendshipController],
exports: [FriendshipService],
})
export class FriendshipsModule {}

View File

@@ -0,0 +1,55 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
import * as session from 'express-session';
import * as passport from 'passport';
import * as redis from 'redis';
import * as connectRedis from 'connect-redis';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true });
const port = process.env.PORT || 3001;
const client = redis.createClient(
{
socket: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT) },
legacyMode: true,
}
);
client.connect();
const RedisStore = connectRedis(session);
client.on('connect', () => {
console.log("Redis successfully Connected");
});
// module afin de créer un pipe de validation qui va nous aider
// à valider les données qui sont envoyées par les utilisateurs
app.useGlobalPipes(
new ValidationPipe({
//permet une liste blanche
whitelist: true,
//interdit les propriétés non autorisées
forbidNonWhitelisted: true,
//permet de transformer les données en fonction de leur type
transform: true,
transformOptions: {//permet de transformer les données en fonction de leur type
enableImplicitConversion: true,
},
}),
);
app.setGlobalPrefix('api/v2');
app.use(
session({
cookie: {
maxAge: 3600000 * 24,
},
secret: process.env.COOKIE_SECRET,
resave: false,
saveUninitialized: false,
store: new RedisStore({ client }),
}),
);
app.use(passport.initialize());
app.use(passport.session());
await app.listen(port, () => { console.log(`Listening on port ${port}`); });
}
bootstrap();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,16 @@
import { IsBoolean, IsEmail, IsOptional, IsString } from 'class-validator';
export class CreateUsersDto {
@IsString()
readonly username: string;
@IsString()
readonly fortyTwoId: string;
@IsEmail()
readonly email: string;
@IsString()
readonly image_url: string;
@IsString()
readonly status: string;
@IsBoolean()
readonly isEnabledTwoFactorAuth: boolean;
}

View File

@@ -0,0 +1,4 @@
import { OmitType, PartialType } from "@nestjs/mapped-types";
import { CreateUsersDto } from "./create-users.dto";
export class PartialUsersDto extends OmitType(CreateUsersDto, ['fortyTwoId', 'email'] as const){}

View File

@@ -0,0 +1,8 @@
// Les types partiels permettent d'importer toutes les variables d'une classe
// et de les mettre comme optionnelles. De plus on peut hériter
// des décorateurs de la classe parente (par exemple @IsString()).
import { OmitType, PartialType } from "@nestjs/mapped-types";
import { CreateUsersDto } from "./create-users.dto";
export class UpdateUsersDto extends OmitType(CreateUsersDto, ['fortyTwoId', 'email', 'image_url', 'status'] as const){}

View File

@@ -0,0 +1,57 @@
import { Exclude } from "class-transformer";
import { IsEmail, Length } from "class-validator";
import { Column, Entity, JoinColumn, JoinTable, ManyToMany, OneToMany, OneToOne, PrimaryGeneratedColumn, Unique } from "typeorm";
import { Friendship } from "../../friendship/entities/friendship.entity";
import { UserStats } from "./userStat.entities";
@Entity('user')
@Unique(['email', 'username'])
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({unique: true})
fortyTwoId: string;
@Column()
username: string;
@Column()
@IsEmail()
email: string;
@Column()
image_url : string;
@Column({ nullable: true })
phone: string;
@Column({ default: 'disconnected' })
status: string;
// @Column()
// isFirstConnection: boolean;
@Column({ default: false, nullable: true })
isEnabledTwoFactorAuth: boolean;
@Column({ default:false, nullable: true })
isTwoFactorAuthenticated: boolean;
@Column({ nullable: true })
secretTwoFactorAuth: string;
@JoinTable()
@OneToMany(type => Friendship , (friendship) => friendship.requesterId)
requesterId: Friendship[];
@JoinTable()
@OneToMany(type => Friendship , (friendship) => friendship.addresseeId)
addresseeId: Friendship[];
@JoinColumn()
@OneToOne(() => UserStats, { cascade: true })
stats: UserStats;
}

View File

@@ -0,0 +1,19 @@
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from "typeorm";
@Entity('stats')
export class UserStats {
@PrimaryGeneratedColumn()
id: number;
@Column({ default: 0 })
winGame: number;
@Column({ default: 0 })
loseGame: number;
@Column({ default: 0 })
drawGame: number;
@Column({ default: 0 })
totalGame: number;
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,99 @@
import {
Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post, Query, Req, Res, UploadedFile, UseGuards, UseInterceptors
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
import { ValidationPipe } from 'src/common/validation/validation.pipe';
import { UpdateUsersDto } from './dto/update-users.dto';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
import { of } from 'rxjs';
import { storageForAvatar } from 'src/common/constants/constants';
@Controller('user')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
// par exemple dans postamn ou insomnia http://localhost:3000/users?limit=10&offset=20
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Get('all')
findAll(@Query() paginationquery : PaginationQueryDto) {
//const { limit, offset } = query;
return this.usersService.findAll(paginationquery);
}
////////////////////////////////////////////////////////
///////////////////////// RUD //////////////////////////
////////////////////////////////////////////////////////
/**
* On ne fait de création via une route
* car un utilisateur est crée à la première connexion avec l'Oauth de 42.
*/
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Get()
findOne(@Req() req) {
return this.usersService.findOne(req.user.id);
}
// GET http://transcendance:8080/user?username=NomDuUser
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Get('search')
findOneByUsername(@Query('username') username: string, @Req() req) {
const user : User = req.user;
return this.usersService.findOneByUsername(user.id.toString(),username);
}
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Get('stats')
getStats(@Req() request) {
return this.usersService.getStats(request.user.id);
}
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Patch()
update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto) {
console.log("DANS PATCH USERS");
return this.usersService.update(req.user.id, usersUpdateDto);
}
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Delete()
remove(@Req() req) {
return this.usersService.remove(req.user.id);
}
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Post('avatar')
@UseInterceptors(FileInterceptor('file', storageForAvatar))
uploadAvatar(@UploadedFile() file, @Req() request){
const user : User = request.user;
this.usersService.updateAvatar(user.id, file.filename);
}
@UseGuards(AuthenticateGuard)
@UseGuards(TwoFactorGuard)
@Get('avatar')
getAvatar(@Req() request, @Res() response) {
const promise = this.usersService.getAvatarUrl(request.user.id).then((url) =>
{
if (url)
return of(response.sendFile(process.cwd() + '/uploads/avatars/' + url));
else
throw new NotFoundException('Avatar not found');
});
return promise;
}
}

View File

@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './entities/user.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Friendship } from '../friendship/entities/friendship.entity';
import { UserStats } from './entities/userStat.entities';
import { FriendshipService } from 'src/friendship/friendship.service';
@Module({
imports: [TypeOrmModule.forFeature([User, Friendship, UserStats])],
providers: [UsersService, FriendshipService],
exports: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,192 @@
import { HttpException, HttpStatus, Injectable, NotFoundException, } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';
import { CreateUsersDto } from './dto/create-users.dto';
import { UpdateUsersDto } from './dto/update-users.dto';
import { Friendship } from '../friendship/entities/friendship.entity';
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
import { UserStats } from './entities/userStat.entities';
import { FriendshipService } from 'src/friendship/friendship.service';
import { stringify } from 'querystring';
// On va devoir sûrement trouver un moyen plus simple pour passer l'id, sûrement via des pipes
// ou des interceptors, mais pour l'instant on va faire comme ça.
@Injectable()
export class UsersService {
constructor(
private readonly friendshipService: FriendshipService,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async findOneByFourtyTwoId(fortytwo_id: string) {
const user = await this.userRepository.findOneBy({fortyTwoId: fortytwo_id});
if (!user)
{
console.log(`The requested user not found.`);
return null;
}
console.log(`The requested user found.`);
return user;
}
async findOne(id: string) {
console.log(`FIND ONE USER SERVICE Find user ${id}`);
const user = await this.userRepository.createQueryBuilder('user')
.leftJoinAndSelect('user.stats', 'stats')
.where('user.id = :id', { id: +id })
.getOne();
if (!user)
throw new NotFoundException(`The requested user not found.`);
console.log(`FIND ONE USER SERVICE The requested user found.`
+ user.stats.id + user.stats.winGame + user.stats.loseGame + user.stats.drawGame + user.stats.totalGame);
const partialUser : Partial<User> = {
username: user.username,
image_url: user.image_url,
isEnabledTwoFactorAuth: user.isEnabledTwoFactorAuth,
status: user.status,
stats: user.stats,
};
return partialUser;
}
async isUsernameExists(usernameToSearch: string): Promise<boolean> {
const user = await this.userRepository.findOneBy({username : usernameToSearch});
if (!user)
return false;
return true;
}
async findOneByUsername(userConnectedId : string, username: string) {
const user : User = await this.userRepository.createQueryBuilder('user')
.leftJoinAndSelect('user.stats', 'stats')
.where('user.username = :username', { username: username })
.getOne();
console.log('USERNAME OF FOUND USER : ' + user.username);
if (!user)
throw new HttpException(`The user could not be found.`,HttpStatus.NOT_FOUND);
if (this.friendshipService.findIfUserIsBlockedOrHasBlocked(userConnectedId, user.id.toString()))
throw new HttpException(`The user could not be found.`,HttpStatus.NOT_FOUND);
const partialUser : Partial<User> = {
username: user.username,
image_url: user.image_url,
status: user.status,
stats: user.stats,
};
return partialUser;
}
// Example : http://localhost:3000/users?limit=10&offset=20
findAll(paginationquery : PaginationQueryDto) {
const { limit, offset } = paginationquery;
return this.userRepository.find({
skip: offset,
take: limit,
});
}
async create(createUserDto: CreateUsersDto) {
if (await this.userRepository.findOneBy({fortyTwoId: createUserDto.fortyTwoId}))
throw new HttpException(`The user already exists.`,HttpStatus.CONFLICT);
const user = this.userRepository.create(createUserDto);
if (!user)
throw new HttpException(`The user could not be created.`,HttpStatus.NOT_FOUND);
user.stats = new UserStats();
return this.userRepository.save(user);
}
async update(id: string, updateUserDto: UpdateUsersDto) {
console.log(`Update user ${id} with ${updateUserDto.isEnabledTwoFactorAuth}`);
const user = await this.userRepository.preload(
{id: +id,
...updateUserDto});
if (!user)
throw new HttpException(`The user could not be updated.`,HttpStatus.NOT_FOUND);
return this.userRepository.save(user);
}
async remove(id: string) {
const user = await this.userRepository.findOneBy({id: +id});
if (!user)
throw new HttpException(`The user could not be deleted.`,HttpStatus.NOT_FOUND);
return this.userRepository.remove(user);
}
async enableTwoFactorAuth(id: string) {
return this.userRepository.update(id, {isEnabledTwoFactorAuth: true, isTwoFactorAuthenticated: true});
}
async setIsTwoFactorAuthenticatedWhenLogout(id: number) {
return this.userRepository.update(id, {isTwoFactorAuthenticated: false});
}
async setAuthenticatorSecret(id: number, secret: string) {
return this.userRepository.update(id, {secretTwoFactorAuth: secret});
}
async updateStatus(id: number, status: string) {
return this.userRepository.update(id, {status: status});
}
async updateAvatar(id: number, avatar: string) {
return this.userRepository.update(id, {image_url: avatar});
}
async getAvatarUrl(id: number) {
const user = await this.userRepository.findOneBy({id: id});
if (!user)
throw new HttpException(`The user could not be found.`,HttpStatus.NOT_FOUND);
if (!user.image_url)
throw new HttpException(`The user has no avatar.`,HttpStatus.NOT_FOUND);
console.log(user.image_url);
return user.image_url;
}
async getStats(id: number) {
const user = await this.userRepository.createQueryBuilder('user')
.leftJoinAndSelect('user.stats', 'stats')
.where('user.id = :id', { id: id })
.getOne();
if (!user.stats || !user)
throw new HttpException(`The user's stats could not be found.`,HttpStatus.NOT_FOUND);
return user.stats;
}
async incrementVictories(id: number) {
const user = await this.userRepository.createQueryBuilder('user')
.leftJoinAndSelect('user.stats', 'stats')
.where('user.id = :id', { id: id })
.getOne();
if (!user.stats || !user)
throw new HttpException(`The user's stats could not be found.`,HttpStatus.NOT_FOUND);
user.stats.winGame++;
user.stats.totalGame++;
this.userRepository.save(user);
}
async incrementDefeats(id: number) {
const user = await this.userRepository.createQueryBuilder('user')
.leftJoinAndSelect('user.stats', 'stats')
.where('user.id = :id', { id: id })
.getOne();
if (!user.stats || !user)
throw new HttpException(`The user's stats could not be found.`,HttpStatus.NOT_FOUND);
user.stats.loseGame++;
user.stats.totalGame++;
this.userRepository.save(user);
}
async incrementDraws(id: number) {
const user = await this.userRepository.createQueryBuilder('user')
.leftJoinAndSelect('user.stats', 'stats')
.where('user.id = :id', { id: id })
.getOne();
if (!user.stats || !user)
throw new HttpException(`The user's stats could not be found.`,HttpStatus.NOT_FOUND);
user.stats.winGame++;
user.stats.totalGame++;
this.userRepository.save(user);
}
}

View File

@@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

View File

@@ -0,0 +1,34 @@
server {
listen 8080 default_server;
listen [::]:8080 default_server;
server_name transcendance;
location /api/v2 {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://backend_dev:3000;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://frontend_dev:8080;
}
}
server {
listen 35729 default_server;
listen [::]:35729 default_server;
server_name transcendance;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://frontend_dev:35729;
}
}

View File

@@ -0,0 +1,10 @@
# Ignore everything
*
# Allow files and folders with a pattern starting with !
!api_front/*.js
!api_front/*.ts
!api_front/*.json
!api_front/*.html
!api_front/*.lock

View File

@@ -0,0 +1,9 @@
FROM node:alpine AS development
WORKDIR /usr/app
COPY ./api_front ./
RUN npm ci
CMD [ "npm", "run", "dev" ]

View File

@@ -0,0 +1,4 @@
/node_modules/
/public/build/
.DS_Store

View File

@@ -0,0 +1,107 @@
# This repo is no longer maintained. Consider using `npm init vite` and selecting the `svelte` option or — if you want a full-fledged app framework and don't mind using pre-1.0 software — use [SvelteKit](https://kit.svelte.dev), the official application framework for Svelte.
---
# svelte app
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
## Get started
Install the dependencies...
```bash
cd svelte-app
npm install
```
...then start [Rollup](https://rollupjs.org):
```bash
npm run dev
```
Navigate to [localhost:8080](http://localhost:8080). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
## Building and running in production mode
To create an optimised version of the app:
```bash
npm run build
```
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
## Single-page app mode
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
```js
"start": "sirv public --single"
```
## Using TypeScript
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
```bash
node scripts/setupTypeScript.js
```
Or remove the script via:
```bash
rm scripts/setupTypeScript.js
```
If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).
## Deploying to the web
### With [Vercel](https://vercel.com)
Install `vercel` if you haven't already:
```bash
npm install -g vercel
```
Then, from within your project folder:
```bash
cd public
vercel deploy --name my-project
```
### With [surge](https://surge.sh/)
Install `surge` if you haven't already:
```bash
npm install -g surge
```
Then, from within your project folder:
```bash
npm run build
surge public my-project.surge.sh
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
{
"name": "svelte-app",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --no-clear --host --port 8080",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@rollup/plugin-typescript": "^8.0.0",
"@tsconfig/svelte": "^2.0.0",
"rollup": "^2.3.4",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0",
"svelte-check": "^2.0.0",
"svelte-preprocess": "^4.0.0",
"tslib": "^2.0.0",
"typescript": "^4.0.0"
},
"dependencies": {
"sirv-cli": "^2.0.0",
"svelte-spa-router": "^3.3.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 134 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 127 KiB

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg>
<metadata>
Created by FontForge 20090914 at Thu Feb 23 16:36:38 2012
By ffonts
Copyright AtushiAoki 1998.All rights recerved.Add&#226;&#128;&#144;FatMan Ver1.0
</metadata>
<defs>
<font id="AddFatMan" horiz-adv-x="740" >
<font-face
font-family="AddFatMan"
font-weight="400"
font-stretch="normal"
units-per-em="1000"
panose-1="0 0 4 0 0 0 0 0 0 0"
ascent="800"
descent="-200"
x-height="592"
cap-height="675"
bbox="0 -33 792 800"
underline-thickness="20"
underline-position="-123"
unicode-range="U+0020-U+007A"
/>
<missing-glyph horiz-adv-x="500"
d="M63 0v800h375v-800h-375zM125 63h250v675h-250v-675z" />
<glyph glyph-name=".notdef" horiz-adv-x="500"
d="M63 0v800h375v-800h-375zM125 63h250v675h-250v-675z" />
<glyph glyph-name=".null" horiz-adv-x="0"
/>
<glyph glyph-name="nonmarkingreturn" horiz-adv-x="327"
/>
<glyph glyph-name="space" unicode=" " horiz-adv-x="327"
/>
<glyph glyph-name="exclam" unicode="!" horiz-adv-x="363"
d="M0 550v-250q0 -76 82 -84h126q84 7 84 84v375h-167q-125 -9 -125 -125zM208 92q78 0 84 83h-84v0h-125v0q-83 -6 -83 -83h208z" />
<glyph glyph-name="comma" unicode="," horiz-adv-x="327"
/>
<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="541"
d="M378 258q77 0 83 84h-83v-1h-292v1q-83 -6 -83 -84h375z" />
<glyph glyph-name="hyphen" unicode="&#x2010;" horiz-adv-x="541"
d="M378 258q77 0 83 84h-83v-1h-292v1q-83 -6 -83 -84h375z" />
<glyph glyph-name="period" unicode="." horiz-adv-x="327"
/>
<glyph glyph-name="zero" unicode="0" horiz-adv-x="739"
d="M542 675h-417q-125 -9 -125 -125v-333q0 -116 125 -125h417q125 9 125 125v333q0 116 -125 125zM292 300q0 78 83 84l128 -1l-198 -198q-13 14 -13 32v83zM583 217q0 -39 -41 -42h-130l171 171v-4v-125z" />
<glyph glyph-name="one" unicode="1" horiz-adv-x="449"
d="M0 550v-167h83v-291h209q83 6 83 83v500h-250q-125 -9 -125 -125z" />
<glyph glyph-name="two" unicode="2" horiz-adv-x="736"
d="M542 675h-417q-116 0 -125 -125v-167h500q77 0 83 -41q0 -39 -41 -42h-459q-77 0 -83 -83v-42q0 -77 83 -83h500q78 0 84 83h-334q-38 0 -41 42h250q116 0 125 125v208q0 116 -125 125z" />
<glyph glyph-name="three" unicode="3"
d="M542 675h-417q-116 0 -125 -125v-167h500q77 0 83 -41q0 -39 -41 -42h-500v-83h541q-3 -42 -41 -42h-542q6 -83 83 -83h500q84 6 84 83v42q0 30 -22 50q22 30 22 75v208q0 116 -125 125z" />
<glyph glyph-name="four" unicode="4" horiz-adv-x="698"
d="M500 550v-250h-208v375h-167q-125 -9 -125 -125v-208q0 -116 125 -125h375v-125q116 0 125 124v459q-116 0 -125 -125z" />
<glyph glyph-name="five" unicode="5" horiz-adv-x="730"
d="M0 550v-208q9 -125 125 -125h458q-3 -42 -41 -42h-542q6 -83 83 -83h500q84 6 84 83v42q-6 83 -84 83h-251h-40v42q6 42 83 42l125 -1q167 9 167 125v167h-542q-125 -9 -125 -125z" />
<glyph glyph-name="six" unicode="6" horiz-adv-x="730"
d="M0 550v-375q6 -83 83 -83h500q84 6 84 83v42q-6 83 -84 83h-251h-40v42q6 42 83 42l125 -1q167 9 167 125v167h-542q-125 -9 -125 -125zM583 217q-3 -42 -41 -42h-250v42h291z" />
<glyph glyph-name="seven" unicode="7" horiz-adv-x="741"
d="M0 550v-167h583v-208q6 -83 84 -83v583h-542q-125 -9 -125 -125z" />
<glyph glyph-name="eight" unicode="8"
d="M542 675h-417q-125 -9 -125 -125v-208q0 -34 19 -70q-19 -26 -19 -55v-42q6 -83 83 -83h500q84 6 84 83v42q0 30 -23 50q23 38 23 75v208q0 116 -125 125zM542 175h-209q-41 3 -41 42h291q-3 -42 -41 -42zM542 300v0h-209q-41 3 -41 42q0 38 41 41h209q41 -3 41 -42
q0 -37 -39 -41h-2v0z" />
<glyph glyph-name="nine" unicode="9"
d="M542 675h-417q-125 -9 -125 -125v-208q0 -116 125 -125h458q-3 -42 -41 -42h-542q6 -83 83 -83h500q84 6 84 83v375q0 116 -125 125zM292 300q0 77 83 83h167q41 -3 41 -41v-42h-291z" />
<glyph glyph-name="colon" unicode=":" horiz-adv-x="187"
d="M63 217q-26 0 -44.5 -18.5t-18.5 -44.5t18.5 -44t44.5 -18q25 0 43.5 18t18.5 44t-18.5 44.5t-43.5 18.5zM63 467q-26 0 -44.5 -18.5t-18.5 -44.5t18.5 -44t44.5 -18q25 0 43.5 18t18.5 44t-18.5 44.5t-43.5 18.5z" />
<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="190"
d="M63 467q-26 0 -44.5 -18.5t-18.5 -44.5t18.5 -44t44.5 -18q25 0 43.5 18t18.5 44t-18.5 44.5t-43.5 18.5zM125 113v41q0 26 -18.5 44.5t-43.5 18.5q-26 0 -44.5 -18.5t-18.5 -44.5q0 -44 42 -59v-66q77 0 83 84z" />
<glyph glyph-name="question" unicode="?" horiz-adv-x="732"
d="M542 300h-292q-77 0 -83 -83h416q78 0 84 83v375h-542q-125 -9 -125 -125v-167h542q38 0 41 -41q-3 -42 -41 -42zM375 175v0h-125v0q-83 -6 -83 -83h208q77 0 83 83h-83z" />
<glyph glyph-name="A" unicode="A" horiz-adv-x="741"
d="M125 92h167v125h208q83 6 83 83h-291q6 83 83 83h208v-208q0 -77 84 -83v583h-542q-125 -9 -125 -125v-333q9 -125 125 -125z" />
<glyph glyph-name="B" unicode="B"
d="M125 92h458q78 0 84 83v42q-3 41 -42 41q42 3 42 42v375h-542q-125 -9 -125 -125v-333q9 -125 125 -125zM375 383h208v-41q-3 -42 -41 -42h-250q6 83 83 83zM292 217h291q-3 -42 -41 -42h-250v42z" />
<glyph glyph-name="C" unicode="C" horiz-adv-x="716"
d="M125 92h458q84 6 84 83h-292q-83 6 -83 83v42q6 83 83 83h125q167 9 167 125v167h-542q-125 -9 -125 -125v-333q9 -125 125 -125z" />
<glyph glyph-name="D" unicode="D" horiz-adv-x="739"
d="M667 217v333q0 116 -125 125h-417q-125 -9 -125 -125v-458h542q125 9 125 125zM292 175v208h208q77 0 83 -83v-42q-6 -83 -83 -83h-208z" />
<glyph glyph-name="E" unicode="E" horiz-adv-x="729"
d="M375 383h125q167 9 167 125v167h-542q-125 -9 -125 -125v-333q9 -125 125 -125h458q84 6 84 83h-292q-52 0 -73 42h281q84 6 84 83h-375q6 83 83 83z" />
<glyph glyph-name="F" unicode="F" horiz-adv-x="716"
d="M125 92h167v125h291q84 6 84 83h-375q6 83 83 83h125q167 9 167 125v167h-542q-125 -9 -125 -125v-333q9 -125 125 -125z" />
<glyph glyph-name="G" unicode="G" horiz-adv-x="731"
d="M375 383h125q167 9 167 125v167h-542q-125 -9 -125 -125v-333q9 -125 125 -125h458q84 6 84 83v125h-250q-84 -6 -84 -83h250q-3 -42 -41 -42h-167q-83 6 -83 83v42q6 83 83 83z" />
<glyph glyph-name="H" unicode="H"
d="M292 299v376h-167q-125 -9 -125 -125v-334q0 -116 125 -125h167v125h166q84 6 84 83h-250zM667 216v459q-116 0 -125 -125v-459q116 0 125 125z" />
<glyph glyph-name="I" unicode="I" horiz-adv-x="365"
d="M83 92h-1h-8h9zM208 92q84 6 84 83v500h-167q-125 -9 -125 -125v-375q0 -76 82 -83h126z" />
<glyph glyph-name="J" unicode="J" horiz-adv-x="719"
d="M83 300q-77 0 -83 -84v-41q6 -84 83 -84h334q83 6 83 84v208q167 9 167 125v167h-542q-125 -9 -125 -125v-167h417v-167q-3 -41 -42 -41h-42q-41 3 -41 41v84h-209z" />
<glyph glyph-name="K" unicode="K"
d="M292 199h208q39 0 42 -42q6 -83 83 -83h42v101q0 52 -29 83q29 32 29 84v315q-116 0 -125 -125v-208q-3 -42 -42 -42h-208v393h-167q-125 -9 -125 -125v-334q0 -116 125 -125h167v108z" />
<glyph glyph-name="L" unicode="L" horiz-adv-x="685"
d="M667 175h-334q-38 0 -41 41v459h-167q-125 -9 -125 -125v-375q0 -78 83 -84h500q78 0 84 84z" />
<glyph glyph-name="M" unicode="M" horiz-adv-x="866"
d="M292 342q0 39 41 42q42 -3 42 -42v-166q0 -78 83 -84h84q77 0 83 84v166q0 39 42 42q41 -3 41 -42v-166q6 -84 84 -84v500q-6 84 -84 84h-83q-77 0 -83 -84v-375q0 -38 -42 -41q-42 3 -42 41v375q-6 84 -83 84l-250 -1q-125 -9 -125 -125v-333q0 -116 125 -125h167v250z
" />
<glyph glyph-name="N" unicode="N" horiz-adv-x="699"
d="M542 592v-376q0 -38 -42 -41q-42 3 -42 41v375q-6 84 -83 84h-250q-125 -9 -125 -125v-334q0 -116 125 -125h167v250q0 39 41 42q42 -3 42 -42v-166q0 -78 83 -84h84q77 0 83 84v0v500q-77 0 -83 -83z" />
<glyph glyph-name="O" unicode="O" horiz-adv-x="739"
d="M667 216v334q0 116 -125 125h-417q-125 -9 -125 -125v-334q0 -116 125 -125h417q125 9 125 125zM292 216v84q0 77 83 83h167q41 -3 41 -42v-125q0 -38 -41 -41h-209q-41 3 -41 41z" />
<glyph glyph-name="P" unicode="P" horiz-adv-x="730"
d="M0 556v-333q9 -125 125 -125h167v125h291q84 6 84 83v375h-542q-125 -9 -125 -125zM375 390h167q38 0 41 -42q-3 -42 -41 -42h-250q6 84 83 84z" />
<glyph glyph-name="Q" unicode="Q" horiz-adv-x="806"
d="M667 300v250q0 116 -125 125h-417q-125 -9 -125 -125v-334q0 -116 125 -125h417q125 9 125 125h83q0 78 -83 84zM583 300h-208q0 -78 83 -84h125q0 -38 -41 -41h-209q-41 3 -41 41v84q0 77 83 83h167q41 -3 41 -42v-41z" />
<glyph glyph-name="R" unicode="R" horiz-adv-x="741"
d="M667 508v167h-542q-125 -9 -125 -125v-334q9 -125 125 -125h167v125h250q38 0 41 -41q6 -84 84 -84v84q0 52 -29 84q29 39 29 82v167zM542 300h-250q6 83 83 83h167q38 0 41 -42q-3 -41 -41 -41z" />
<glyph glyph-name="S" unicode="S" horiz-adv-x="731"
d="M375 383h292v167q0 116 -125 125h-417q-125 -9 -125 -125v-209q9 -125 125 -125h458q-3 -41 -41 -41h-542q6 -84 83 -84h500q84 6 84 84v41q-6 84 -84 84h-251h-40v41q6 42 83 42z" />
<glyph glyph-name="T" unicode="T" horiz-adv-x="711"
d="M375 91v292h125q167 9 167 125v167h-542q-125 -9 -125 -125v-167h292v-208q6 -84 83 -84z" />
<glyph glyph-name="U" unicode="U" horiz-adv-x="699"
d="M542 91q77 0 83 84v0v500q-77 0 -83 -84v-374q-6 -39 -42 -42h-167q-38 0 -41 42v458h-167q-125 -9 -125 -125v-375q0 -76 82 -84h460z" />
<glyph glyph-name="V" unicode="V" horiz-adv-x="737"
d="M667 342v333q-78 0 -84 -83v-250q0 -105 -83 -146q-35 -18 -83 -21h-84q-38 0 -41 42v458h-167q-125 -9 -125 -125v-333q0 -116 125 -125h292q156 0 218 125q26 52 32 125z" />
<glyph glyph-name="W" unicode="W" horiz-adv-x="865"
d="M625 92h83q78 0 84 83v500q-78 0 -84 -83v-369q0 -38 -41 -41q-42 3 -42 41v369q-6 83 -83 83h-84q-83 -6 -83 -83v-369q0 -38 -42 -41q-41 3 -41 41v452h-167q-125 -9 -125 -125v-333q0 -116 125 -125h250q77 0 83 83v167q0 38 42 41q42 -3 42 -41v-167q6 -83 83 -83z
" />
<glyph glyph-name="X" unicode="X" horiz-adv-x="741"
d="M292 175q3 42 41 42h167q39 0 42 -42q6 -83 83 -83h42v83q0 52 -29 84q29 31 29 83v333q-116 0 -125 -125v-208q-3 -42 -42 -42h-167q-41 3 -41 42v333h-167q-125 -9 -125 -125v-208q0 -77 83 -83q-77 0 -83 -84v-83h208q78 0 84 83z" />
<glyph glyph-name="Y" unicode="Y" horiz-adv-x="732"
d="M333 101h84v125h166q78 0 84 84v375q-78 0 -84 -84v-250q-3 -41 -41 -41h-209q-41 3 -41 41v334h-167q-125 -9 -125 -125v-209q0 -116 83 -125h250v-125z" />
<glyph glyph-name="Z" unicode="Z" horiz-adv-x="738"
d="M583 176h-291q0 39 41 42l250 -1q78 0 84 84v375h-542q-125 -9 -125 -125v-167h542q38 0 41 -42q-3 -41 -41 -41h-459q-77 0 -83 -84v-124h667q0 77 -84 83z" />
<glyph glyph-name="underscore" unicode="_" horiz-adv-x="538"
d="M375 175v0h-292v0q-83 -6 -83 -83h375q77 0 83 83h-83z" />
<glyph glyph-name="a" unicode="a" horiz-adv-x="656"
d="M583 175v417h-458q-125 -9 -125 -125v-250q9 -125 125 -125h292q83 6 83 83h-208v125q6 83 83 83h125v-208q0 -77 83 -83v83z" />
<glyph glyph-name="b" unicode="b" horiz-adv-x="651"
d="M125 675q-125 -9 -125 -125v-333q0 -116 125 -125h333q125 9 125 125v250q0 116 -125 125h-166v83h-167zM375 384l83 -1q42 -3 42 -41v-125q0 -39 -42 -42h-125q-41 3 -41 42v83q0 78 83 84z" />
<glyph glyph-name="c" unicode="c" horiz-adv-x="635"
d="M583 175h-208q-83 6 -83 83v42q6 83 83 83h83q125 4 125 84v125h-458q-125 -9 -125 -125v-250q9 -125 125 -125h375q83 6 83 83z" />
<glyph glyph-name="d" unicode="d" horiz-adv-x="656"
d="M458 92q125 9 125 125v458q-77 0 -83 -83h-375q-125 -9 -125 -125v-250q0 -116 125 -125h333zM292 300q0 78 83 84l83 -1q42 -3 42 -41v-125q0 -39 -42 -42h-125q-41 3 -41 42v83z" />
<glyph glyph-name="e" unicode="e" horiz-adv-x="651"
d="M0 467v-250q0 -116 125 -125h333q125 9 125 125h-83q0 -39 -42 -42h-125q-41 3 -41 42v41h291v209q0 116 -125 125h-333q-125 -9 -125 -125zM375 384l83 -1q42 -3 42 -41h-198q21 42 73 42z" />
<glyph glyph-name="f" unicode="f" horiz-adv-x="676"
d="M83 133q9 -125 125 -125h167v167q83 6 83 83h-83q6 84 83 84h42q125 6 125 83v167h-417q-125 -9 -125 -125v-209q-83 -6 -83 -83h83v-42z" />
<glyph glyph-name="g" unicode="g" horiz-adv-x="655"
d="M458 592h-333q-125 -9 -125 -125v-250q0 -116 125 -125h375q-3 -42 -42 -42h-458q0 -77 83 -83h417q83 6 83 83v417q0 116 -125 125zM500 217q0 -39 -42 -42h-125q-41 3 -41 42v83q0 78 83 84l83 -1q42 -3 42 -41v-125z" />
<glyph glyph-name="h" unicode="h" horiz-adv-x="652"
d="M583 217v250q0 116 -125 125h-166v83h-167q-125 -9 -125 -125v-333q0 -116 125 -125h167v208q0 78 83 84l83 -1q42 -3 42 -41v-250q77 0 83 83v42z" />
<glyph glyph-name="i" unicode="i" horiz-adv-x="363"
d="M83 92h-1h-8h9zM208 92q84 6 84 83v375h-167q-125 -9 -125 -125v-250q0 -76 82 -83h126zM83 675q-83 -6 -83 -83h208q84 6 84 83h-209z" />
<glyph glyph-name="j" unicode="j" horiz-adv-x="614"
d="M542 133v417q-84 -6 -84 -83v-333q0 -39 -41 -42h-21h-21q-83 6 -83 83v125h-167q-125 -9 -125 -125v-42q0 -116 125 -125h292q125 9 125 125zM625 675h-208q-84 -6 -84 -83h209q83 6 83 83z" />
<glyph glyph-name="k" unicode="k" horiz-adv-x="654"
d="M125 85h167v108h125q38 0 41 -42q6 -83 84 -83h41v101q0 51 -28 83q28 32 28 84v249q-116 0 -125 -125v-142q-3 -42 -41 -42h-125v392h-167q-125 -8 -125 -125v-333q0 -116 125 -125z" />
<glyph glyph-name="l" unicode="l" horiz-adv-x="475"
d="M417 169h-84q-38 0 -41 41v458h-167q-125 -8 -125 -125v-374q0 -78 83 -84h250q78 0 84 84z" />
<glyph glyph-name="m" unicode="m" horiz-adv-x="863"
d="M792 168v292q0 116 -125 125h-542q-125 -9 -125 -125v-250q0 -116 125 -125h167v209q0 77 83 83h21h21q41 -3 41 -42v-243h84v202q0 77 83 83h21h21q41 -3 41 -42v-250q78 0 84 83z" />
<glyph glyph-name="n" unicode="n" horiz-adv-x="655"
d="M583 168v292q0 116 -125 125h-333q-125 -9 -125 -125v-250q0 -116 125 -125h167v209q0 77 83 83h83q42 -3 42 -42v-250q77 0 83 83z" />
<glyph glyph-name="o" unicode="o" horiz-adv-x="654"
d="M458 592h-333q-125 -9 -125 -125v-250q0 -116 125 -125h333q125 9 125 125v250q0 116 -125 125zM500 217q0 -39 -42 -42h-125q-41 3 -41 42v83q0 78 83 84l83 -1q42 -3 42 -41v-125z" />
<glyph glyph-name="p" unicode="p" horiz-adv-x="654"
d="M0 217v-84q0 -116 125 -125h167v84h166q125 9 125 125v250q0 116 -125 125h-333q-125 -9 -125 -125v-250zM292 300q0 78 83 84l83 -1q42 -3 42 -41v-125q0 -39 -42 -42h-125q-41 3 -41 42v83z" />
<glyph glyph-name="q" unicode="q" horiz-adv-x="655"
d="M583 133v334q0 116 -125 125h-333q-125 -9 -125 -125v-250q0 -116 125 -125h375v-84q83 9 83 125zM458 175h-125q-41 3 -41 42v83q0 77 83 83h83q42 -3 42 -41v-125q0 -39 -42 -42z" />
<glyph glyph-name="r" unicode="r" horiz-adv-x="622"
d="M125 92h167v208q0 78 83 84l208 -1v84q0 116 -125 125h-333q-125 -9 -125 -125v-250q0 -116 125 -125z" />
<glyph glyph-name="s" unicode="s" horiz-adv-x="647"
d="M583 175v42q-6 83 -83 83h-168h-40v42q6 42 83 42l42 -1q166 6 166 84v125h-458q-125 -9 -125 -125v-125q9 -125 125 -125h375q-3 -42 -42 -42h-458q6 -83 83 -83h417q83 6 83 83z" />
<glyph glyph-name="t" unicode="t" horiz-adv-x="630"
d="M333 592v83q-25 -2 -47 -10q-52 -21 -78 -73h-83q-125 -9 -125 -125v-84h208v-166q9 -125 125 -125h84q83 6 83 83h-125q-42 3 -42 42v166h84q166 9 166 125v84h-250z" />
<glyph glyph-name="u" unicode="u" horiz-adv-x="614"
d="M458 508v-333h-125q-38 0 -41 41v376l-167 -1q-125 -9 -125 -125v-291q0 -77 82 -84h376q78 0 84 84v417q-78 0 -84 -84z" />
<glyph glyph-name="v" unicode="v" horiz-adv-x="651"
d="M583 342v250q-77 0 -83 -84v-166q0 -105 -83 -146q-35 -18 -84 -21q-38 0 -41 42v375h-167q-125 -9 -125 -125v-250q0 -116 125 -125h208q157 0 219 125q26 52 31 125z" />
<glyph glyph-name="w" unicode="w" horiz-adv-x="864"
d="M708 509v-286q0 -38 -41 -41q-42 3 -42 41v286q-6 83 -83 83h-84q-83 -6 -83 -83v-286q0 -38 -42 -41q-41 3 -41 41v369h-167q-125 -9 -125 -125v-250q0 -116 125 -125h250q77 0 83 83v167q0 38 42 41q42 -3 42 -41v-167q6 -83 83 -83h83q78 0 84 83v417q-78 0 -84 -83z
" />
<glyph glyph-name="x" unicode="x" horiz-adv-x="656"
d="M458 467v-125q-3 -42 -41 -42h-84q-41 3 -41 42v250h-167q-125 -8 -125 -125v-125q0 -77 83 -83q-77 0 -83 -84v-83h208q78 0 84 83q3 42 41 42h84q38 0 41 -42q6 -83 84 -83h41v83q0 52 -28 84q28 31 28 83v250q-116 0 -125 -125z" />
<glyph glyph-name="y" unicode="y" horiz-adv-x="656"
d="M500 509v-292q-3 -42 -42 -42h-125q-41 3 -41 42v375h-167q-125 -9 -125 -125v-250q0 -116 83 -125h417q-3 -42 -42 -42h-458q0 -77 83 -83h417q83 6 83 83v542q-77 0 -83 -83z" />
<glyph glyph-name="z" unicode="z" horiz-adv-x="653"
d="M0 467v-84h458q39 0 42 -42q-3 -41 -42 -41h-375q-77 0 -83 -84v-41q0 -78 83 -84h417q77 0 83 84h-250q-38 0 -41 41h208q77 0 83 84v292h-458q-125 -9 -125 -125z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 134 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -0,0 +1,512 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg>
<metadata>
Created by FontForge 20120731 at Tue Oct 18 10:48:08 2022
By ffonts
Copyright (c) 2021, Khalid M. All right reserved.
Behance.com/khalydm
</metadata>
<defs>
<font id="Monocode-Regular-V01.02b" horiz-adv-x="600" >
<font-face
font-family="Monocode"
font-weight="400"
font-stretch="normal"
units-per-em="1000"
panose-1="2 0 5 9 0 0 0 0 0 0"
ascent="800"
descent="-200"
x-height="535"
cap-height="788"
bbox="25 -132 575 791"
underline-thickness="50"
underline-position="-50"
unicode-range="U+0020-00FF"
/>
<missing-glyph
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".notdef"
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".null" horiz-adv-x="0"
/>
<glyph glyph-name="nonmarkingreturn"
/>
<glyph glyph-name="space" unicode=" "
/>
<glyph glyph-name="exclam" unicode="!"
d="M338 170h-76v618h76v-618zM338 91v-93h-76v93h76z" />
<glyph glyph-name="quotedbl" unicode="&#x22;"
d="M282 788v-93q0 -36 -32 -52q-13 -6 -26 -6v34q18 0 23 18q1 3 1 6h-43v93h77zM318 788h77v-93q0 -36 -32 -52q-13 -6 -26 -6v34q18 0 23 18q1 3 1 6h-43v93z" />
<glyph glyph-name="numbersign" unicode="#"
d="M532 470h-68l-23 -152h68l-11 -76h-69l-37 -242h-76l37 242h-152l-37 -242h-76l36 242h-68l12 76h68l23 152h-68l11 76h69l37 242h76l-37 -242h152l37 242h76l-36 -242h68zM364 318l23 152h-151l-23 -152h151z" />
<glyph glyph-name="dollar" unicode="$"
d="M502 578h-77v42q0 11 -18 16q-5 2 -11 2h-58v-133h58q59 0 90 -44q16 -24 16 -50v-243q0 -51 -48 -79q-27 -15 -58 -16h-58v-75h-76v75h-58q-59 0 -90 45q-16 24 -16 50v42h77v-42q0 -11 18 -17q6 -1 11 -1h58v279h-58q-59 0 -90 45q-16 23 -16 50v96q0 51 48 79
q27 15 58 15h58v74h76v-74h58q59 0 90 -44q16 -24 16 -50v-42zM396 429h-58v-279h58q24 4 29 18v243q0 11 -18 16q-6 2 -11 2zM262 505v133h-58q-24 -4 -29 -18v-96q0 -12 18 -17q6 -1 11 -2h58z" />
<glyph glyph-name="percent" unicode="%"
d="M202 525h-30q-51 0 -78 44q-14 23 -14 49v78q0 51 44 78q23 14 48 14h30q52 0 79 -44q13 -23 14 -48v-78q0 -52 -44 -79q-23 -13 -49 -14zM172 711q-13 -3 -15 -15v-78q3 -13 15 -16h30q13 3 16 16v78q-3 13 -16 15h-30zM428 0h-30q-52 0 -79 44q-13 23 -14 48v78
q0 52 44 79q23 13 49 14h30q51 0 78 -45q14 -22 14 -48v-78q0 -51 -44 -78q-23 -14 -48 -14zM398 186q-13 -3 -16 -16v-78q0 -13 12 -15q3 0 4 -1h30q13 4 15 16v78q-3 13 -15 16h-30zM520 788l-363 -788h-77l363 788h77z" />
<glyph glyph-name="ampersand" unicode="&#x26;"
d="M497 -50h-88l-31 55q-16 -5 -31 -5h-138q-57 0 -88 48q-17 27 -18 58v156q0 57 48 88q14 9 29 14l-25 46q-13 24 -13 51v149q0 57 47 88q27 18 58 18h67q56 0 88 -48q17 -27 17 -58v-205h-76v205q0 22 -22 28q-4 1 -7 1h-67q-22 0 -28 -21q-1 -4 -1 -8v-149q1 -8 4 -14
l44 -79v-1l110 -199v113h76v-175q0 -28 -13 -52zM209 77h130l-119 214h-11q-22 0 -28 -21q-1 -5 -1 -8v-156q0 -22 21 -28q5 -1 8 -1z" />
<glyph glyph-name="quotesingle" unicode="'"
d="M262 791h76v-92q0 -36 -32 -52q-12 -6 -26 -6v33q20 7 24 25h-42v92z" />
<glyph glyph-name="parenleft" unicode="("
d="M372 0q-86 0 -141 67q-41 50 -41 115v424q0 86 67 141q51 41 115 41h38v-77h-38q-56 0 -88 -46q-17 -27 -17 -59v-424q0 -57 46 -88q27 -17 59 -18v-76z" />
<glyph glyph-name="parenright" unicode=")"
d="M228 0h-38v76h38q56 0 88 47q17 27 17 59v424q0 57 -46 88q-27 17 -59 17h-38v77h38q86 0 141 -67q41 -51 41 -115v-424q0 -86 -67 -141q-51 -41 -115 -41z" />
<glyph glyph-name="asterisk" unicode="*"
d="M414 677l-52 -16l32 -45l-62 -45l-32 45l-32 -45l-62 45l32 45l-52 16l24 73l52 -17v55h76v-55l52 17z" />
<glyph glyph-name="plus" unicode="+"
d="M338 476v-138h138v-76h-138v-138h-76v138h-138v76h138v138h76z" />
<glyph glyph-name="comma" unicode=","
d="M262 92h76v-93q0 -36 -32 -52q-12 -6 -26 -6v34q20 6 24 24h-42v93z" />
<glyph glyph-name="hyphen" unicode="-"
d="M182 262v76h236v-76h-236z" />
<glyph glyph-name="period" unicode="."
d="M262 0v93h76v-93h-76z" />
<glyph glyph-name="slash" unicode="/"
d="M191 0h-77l295 788h77z" />
<glyph glyph-name="zero" unicode="0"
d="M419 788q57 0 88 -48q16 -26 16 -57v-578q0 -57 -47 -88q-26 -16 -57 -17h-238q-57 0 -88 48q-16 26 -16 57v578q0 57 47 88q26 16 57 17h238zM181 711q-21 0 -27 -20q-1 -4 -1 -8v-480l282 503q-8 5 -16 5h-238zM419 77q21 0 27 20q1 4 1 8v467l-276 -493q5 -2 10 -2
h238z" />
<glyph glyph-name="one" unicode="1"
d="M338 76h182v-76h-440v76h182v607l-123 -123l-55 54l174 173h80v-711z" />
<glyph glyph-name="two" unicode="2"
d="M519 0h-439v172l354 333q9 9 9 21v156q0 22 -21 28q-5 1 -8 1h-228q-22 0 -28 -22q-1 -4 -1 -7v-39h-77v39q0 56 48 88q27 17 58 17h228q57 0 88 -47q17 -27 18 -58v-156q-1 -45 -33 -77l-330 -310v-63h286v68h76v-144z" />
<glyph glyph-name="three" unicode="3"
d="M496 497q24 -30 24 -67v-324q-1 -57 -48 -88q-27 -18 -58 -18h-228q-57 0 -88 47q-18 28 -18 59v39h77v-39q0 -22 21 -28l8 -2h228q22 0 28 22q1 4 1 8v324q0 12 -8 20q-9 8 -21 9h-138h-39v76h39h138q12 1 21 9q8 9 8 21v117q0 22 -21 28q-5 1 -8 1h-228q-22 0 -28 -22
q-1 -4 -1 -7v-39h-77v39q0 56 48 88q27 17 58 17h228q57 0 88 -47q17 -27 18 -58v-117q-1 -39 -24 -68z" />
<glyph glyph-name="four" unicode="4"
d="M443 787h77v-594v-76v-117h-77v117h-363v61l192 610l80 -1l-187 -594h278v594z" />
<glyph glyph-name="five" unicode="5"
d="M414 1h-228q-57 0 -88 47q-17 27 -18 58h77q0 -22 21 -28q5 -1 8 -1h228q12 1 20 9q9 9 9 21l-1 323q0 22 -21 29q-5 0 -8 1h-333v328h440v-144h-77v68h-286v-176h256q57 0 88 -47q17 -27 18 -58l1 -324q-1 -44 -31 -75q-32 -31 -75 -31z" />
<glyph glyph-name="six" unicode="6"
d="M520 644h-77v38q0 22 -21 28l-8 2h-228q-22 0 -28 -22q-1 -4 -1 -8v-150q15 4 29 4h228q57 0 88 -47q17 -27 18 -59v-324q0 -56 -48 -88q-27 -17 -58 -17h-228q-57 0 -88 47q-17 27 -18 58v576q0 57 48 88q27 18 58 18h228q57 0 88 -47q17 -28 18 -59v-38zM186 460
q-22 0 -28 -22q-1 -4 -1 -8v-324q0 -22 21 -28q4 -1 8 -1h228q22 0 28 22q1 4 1 7v324q0 22 -21 29q-4 0 -8 1h-228z" />
<glyph glyph-name="seven" unicode="7"
d="M520 788v-174l-352 -614h-88l364 634v78h-287v-68h-76v144h439z" />
<glyph glyph-name="eight" unicode="8"
d="M520 682v-117q-1 -38 -25 -67q24 -30 25 -68v-324q0 -56 -48 -88q-27 -17 -58 -17h-228q-57 0 -88 47q-17 27 -18 58v324q1 39 25 68q-24 30 -25 67v117q0 57 48 88q27 18 58 18h228q57 0 88 -47q17 -28 18 -59zM157 682v-117q0 -22 21 -28q5 -1 8 -1h228q22 0 28 21
q1 5 1 8v117q0 22 -21 28q-5 1 -8 2h-228q-22 0 -28 -22q-1 -4 -1 -8zM443 106v324q0 22 -21 29q-5 0 -8 1h-228q-22 0 -28 -22q-1 -4 -1 -8v-324q0 -22 21 -28q5 -1 8 -1h228q22 0 28 22q1 4 1 7z" />
<glyph glyph-name="nine" unicode="9"
d="M414 788q57 0 88 -47q17 -28 18 -59v-576q0 -56 -48 -88q-27 -17 -58 -17h-228q-44 1 -75 31q-30 31 -31 74v39h77v-39q1 -12 8 -20q10 -8 21 -9h228q22 0 28 22q1 4 1 7v151q-15 -4 -29 -4h-228q-57 0 -88 47q-17 27 -18 58v324q0 57 48 88q27 18 58 18h228zM443 358
v324q0 22 -21 28l-8 2h-228q-22 0 -28 -22q-1 -4 -1 -8v-324q0 -22 21 -28q4 -1 8 -1h228q22 0 28 21q1 5 1 8z" />
<glyph glyph-name="colon" unicode=":"
d="M338 0h-76v93h76v-93zM338 342v-93h-76v93h76z" />
<glyph glyph-name="semicolon" unicode=";"
d="M338 249h-76v93h76v-93zM262 93h76v-93q0 -36 -32 -52q-12 -6 -26 -6v34q20 6 24 24h-42v93z" />
<glyph glyph-name="less" unicode="&#x3c;"
d="M348 159l-145 121v40l145 121l49 -59l-99 -82l99 -82z" />
<glyph glyph-name="equal" unicode="="
d="M124 191v77h352v-77h-352zM124 332v77h352v-77h-352z" />
<glyph glyph-name="greater" unicode="&#x3e;"
d="M252 159l-49 59l99 82l-99 82l49 59l145 -121v-40z" />
<glyph glyph-name="question" unicode="?"
d="M338 -2h-76v93h76v-93zM338 298v-128h-76v128q0 57 47 88q27 17 59 18h46q22 0 28 21q1 4 1 8v249q0 22 -21 28q-4 1 -8 1h-228q-22 0 -28 -21q-1 -4 -1 -8v-38h-77v38q0 57 48 88q27 18 58 18h228q57 0 88 -48q18 -27 18 -58v-249q0 -57 -48 -88q-27 -18 -58 -18h-46
q-23 0 -29 -21q-1 -5 -1 -8z" />
<glyph glyph-name="at" unicode="@"
d="M497 181h39v-76h-39q-56 0 -88 47q-1 2 -2 3q-28 -19 -61 -19h-58q-57 0 -88 47q-17 27 -18 58v194q0 56 48 88q27 17 58 17h58q24 0 46 -10v64q0 22 -22 28q-4 1 -8 1h-192q-22 0 -28 -21q-1 -5 -1 -8v-486q0 -22 21 -28q5 -1 8 -1h263v-76h-263q-57 0 -88 47
q-17 27 -18 58v486q0 57 48 88q27 17 58 17h192q57 0 89 -47q17 -27 17 -58v-384q0 -22 21 -28q5 -1 8 -1zM375 241v194q0 22 -21 28q-5 1 -8 1h-58q-22 0 -28 -22q-1 -4 -1 -7v-194q0 -22 21 -28q5 -1 8 -1h58q22 0 28 21q1 5 1 8z" />
<glyph glyph-name="A" unicode="A"
d="M442 0l-28 129h-228l-28 -129h-79l172 788h98l172 -788h-79zM203 206h194l-97 447z" />
<glyph glyph-name="B" unicode="B"
d="M520 682v-117q0 -38 -24 -67q24 -30 24 -68v-324q0 -57 -47 -88q-27 -18 -58 -18h-335v788h335q56 0 88 -48q17 -27 17 -58zM156 711v-175h259q22 0 28 21q1 5 1 8v117q0 22 -21 28q-5 1 -8 1h-259zM444 106v324q0 22 -21 28q-5 1 -8 1h-259v-382h259q22 0 28 21q1 4 1 8
z" />
<glyph glyph-name="C" unicode="C"
d="M414 0h-228q-57 0 -88 48q-18 27 -18 58v576q0 57 48 88q27 18 58 18h228q57 0 88 -48q18 -27 18 -58v-38h-77v38q0 22 -21 28q-4 1 -8 1h-228q-22 0 -28 -21q-1 -4 -1 -8v-576q0 -22 21 -28q4 -1 8 -1h228q22 0 28 21q1 4 1 8v38h77v-38q0 -57 -48 -88q-27 -18 -58 -18z
" />
<glyph glyph-name="D" unicode="D"
d="M414 788q57 0 88 -48q18 -27 18 -58v-576q0 -57 -48 -88q-27 -18 -58 -18h-334v788h334zM443 106v576q0 22 -21 28q-4 1 -8 1h-257v-634h257q22 0 28 21q1 4 1 8z" />
<glyph glyph-name="E" unicode="E"
d="M520 711h-363v-175h363v-77h-363v-382h363v-77h-440v788h440v-77z" />
<glyph glyph-name="F" unicode="F"
d="M520 711h-363v-175h363v-77h-363v-459h-77v459v77v252h440v-77z" />
<glyph glyph-name="G" unicode="G"
d="M414 0h-228q-57 0 -88 48q-18 27 -18 58v576q0 57 48 88q27 18 58 18h228q57 0 88 -48q18 -27 18 -58v-38h-77v38q0 22 -21 28q-4 1 -8 1h-228q-22 0 -28 -21q-1 -4 -1 -8v-576q0 -22 21 -28q4 -1 8 -1h228q22 0 28 21q1 4 1 8v353h-123v77h200v-430q0 -57 -48 -88
q-27 -18 -58 -18z" />
<glyph glyph-name="H" unicode="H"
d="M443 788h77v-788h-77v459h-286v-459h-77v788h77v-252h286v252z" />
<glyph glyph-name="I" unicode="I"
d="M520 711h-182v-634h182v-77h-440v77h182v634h-182v77h440v-77z" />
<glyph glyph-name="J" unicode="J"
d="M414 0h-228q-57 0 -88 48q-18 27 -18 58v130h77v-130q0 -22 21 -28q4 -1 8 -1h228q22 0 28 21q1 4 1 8v682h77v-682q0 -57 -48 -88q-27 -18 -58 -18z" />
<glyph glyph-name="K" unicode="K"
d="M482 711q-27 0 -45 -19l-268 -299l268 -297q18 -19 44 -19h39v-77h-39q-60 1 -100 45l-224 247v-292h-77v788h77v-295l224 250q41 44 101 45h38v-77h-38z" />
<glyph glyph-name="L" unicode="L"
d="M520 0h-440v788h77v-711h363v-77z" />
<glyph glyph-name="M" unicode="M"
d="M558 0h-77l1 669l-183 -234l-181 234l1 -669h-77l-1 788h81l178 -228l178 228h81z" />
<glyph glyph-name="N" unicode="N"
d="M520 0h-76l-287 608v-608h-77v788h76l287 -608v608h77v-788z" />
<glyph glyph-name="O" unicode="O"
d="M414 0h-228q-57 0 -88 48q-18 27 -18 58v576q0 57 48 88q27 18 58 18h228q57 0 88 -48q18 -27 18 -58v-576q0 -57 -48 -88q-27 -18 -58 -18zM186 711q-22 0 -28 -21q-1 -4 -1 -8v-576q0 -22 21 -28q4 -1 8 -1h228q22 0 28 21q1 4 1 8v576q0 22 -21 28q-4 1 -8 1h-228z
" />
<glyph glyph-name="P" unicode="P"
d="M414 788q57 0 88 -48q18 -27 18 -58v-117q0 -57 -48 -88q-27 -17 -58 -18h-257v-459h-77v788h334zM443 565v117q0 22 -21 28q-4 1 -8 1h-257v-175h257q22 0 28 21q1 5 1 8z" />
<glyph glyph-name="Q" unicode="Q"
d="M515 106q0 -57 -46 -89q-26 -17 -57 -17h-46q44 -55 112 -55h37v-77h-37q-98 0 -164 76q-22 26 -35 56h-91q-55 0 -86 47q-17 28 -17 59v576q0 57 46 88q27 18 57 18h224q55 0 86 -48q17 -27 17 -58v-576zM334 92q0 -8 1 -16h77q21 0 27 22q1 4 1 8v576q0 22 -21 28
q-4 1 -7 1h-224q-21 0 -27 -21q-1 -4 -1 -8v-576q0 -22 21 -28q4 -1 7 -2h72q-1 9 -1 16v38h75v-38z" />
<glyph glyph-name="R" unicode="R"
d="M520 682v-117q-1 -38 -24 -67q23 -30 24 -67v-431h-77v431q0 21 -21 27q-4 1 -8 1h-257v-459h-77v788h334q57 0 88 -48q18 -27 18 -58zM157 536h257q22 0 28 21q1 5 1 8v117q0 22 -21 28q-4 1 -8 1h-257v-175z" />
<glyph glyph-name="S" unicode="S"
d="M414 1h-228q-56 0 -88 47q-17 28 -17 59v38h76v-38q0 -22 21 -28q5 -1 8 -1h228q22 0 28 21q1 4 1 8v323q0 22 -21 28q-4 1 -8 1h-228q-44 1 -75 31q-30 32 -31 75l1 117q0 56 47 88q27 17 58 17h228q57 0 88 -47q17 -27 18 -58v-38h-77v38q0 22 -21 28q-4 1 -8 1h-228
q-22 0 -28 -21q-1 -5 -1 -8v-117q1 -12 8 -21q10 -8 21 -8h228q57 0 88 -48q17 -27 18 -58v-323q0 -57 -48 -88q-27 -18 -58 -18z" />
<glyph glyph-name="T" unicode="T"
d="M519 787v-76h-181v-710h-76v710h-181v76h438z" />
<glyph glyph-name="U" unicode="U"
d="M414 1h-228q-56 0 -88 47q-17 28 -17 59v680h76v-680q0 -22 21 -28q5 -1 8 -1h228q22 0 28 21q1 4 1 8v680h76v-680q0 -57 -47 -88q-27 -18 -58 -18z" />
<glyph glyph-name="V" unicode="V"
d="M520 787l-171 -787h-98l-171 787h78l142 -652l142 652h78z" />
<glyph glyph-name="W" unicode="W"
d="M43 788h76v-667l182 233l180 -233v667h76l1 -786h-81l-177 227l-177 -227h-81z" />
<glyph glyph-name="X" unicode="X"
d="M522 787l-180 -396l178 -390h-84l-136 298l-136 -298h-84l178 390l-180 396h84l138 -304l138 304h84z" />
<glyph glyph-name="Y" unicode="Y"
d="M521 787l-183 -300v-487h-76v487l-183 300h89l132 -216l132 216h89z" />
<glyph glyph-name="Z" unicode="Z"
d="M519 1h-438v71l349 638h-349v77h438v-72l-349 -638h349v-76zM119 63z" />
<glyph glyph-name="bracketleft" unicode="["
d="M410 0h-220v788h220v-77h-143v-635h143v-76z" />
<glyph glyph-name="backslash" unicode="\"
d="M409 0l-295 788h77l295 -788h-77z" />
<glyph glyph-name="bracketright" unicode="]"
d="M410 0h-220v76h143v635h-143v77h220v-788z" />
<glyph glyph-name="asciicircum" unicode="^"
d="M349 611l-49 73l-49 -73l-64 43l91 134h44l91 -134z" />
<glyph glyph-name="underscore" unicode="_"
d="M80 0v77h440v-77h-440z" />
<glyph glyph-name="grave" unicode="`"
d="M328 788l21 -114h-49l-49 114h77z" />
<glyph glyph-name="a" unicode="a"
d="M536 0h-39q-38 1 -67 25q-31 -25 -68 -25h-192q-57 0 -88 48q-17 27 -18 58v138q0 57 48 88q27 17 58 18h221v80q0 22 -21 28q-4 1 -8 1h-192q-22 0 -28 -21q-1 -4 -1 -8v-38h-77v38q0 57 48 88q27 18 58 18h192q57 0 88 -48q17 -27 18 -58v-324q0 -22 21 -28q5 -1 8 -1
h39v-77zM362 77q22 0 28 21q1 4 1 8v167h-221q-22 0 -28 -21q-1 -4 -1 -8v-138q0 -22 21 -28q5 -1 8 -1h192z" />
<glyph glyph-name="b" unicode="b"
d="M430 536q57 0 88 -48q17 -27 18 -58v-324q0 -57 -48 -88q-27 -18 -58 -18h-192q-39 1 -68 25q-30 -25 -67 -25h-39v77h38q23 0 29 21q1 4 1 8v682h76v-256q15 4 30 4h192zM459 106v324q0 22 -21 28q-5 1 -8 1h-192q-22 0 -28 -21l-2 -8v-324q0 -22 22 -28q4 -1 8 -1h192
q22 0 28 21q1 4 1 8z" />
<glyph glyph-name="c" unicode="c"
d="M396 0h-192q-57 0 -88 48q-18 27 -18 58v324q0 57 48 88q27 18 58 18h192q57 0 88 -48q18 -27 18 -58v-38h-77v38q0 22 -21 28q-4 1 -8 1h-192q-22 0 -28 -21q-1 -4 -1 -8v-324q0 -22 21 -28q4 -1 8 -1h192q22 0 28 21q1 4 1 8v38h77v-38q0 -57 -48 -88q-27 -18 -58 -18z
" />
<glyph glyph-name="d" unicode="d"
d="M536 0h-38q-39 1 -68 25q-30 -25 -68 -25h-192q-57 0 -88 48q-17 27 -18 58v324q0 57 48 88q27 18 58 18h192q15 0 29 -4v256h77v-682q0 -22 21 -28q5 -1 8 -1h38zM362 77q22 0 28 21q1 4 1 8v324q0 22 -21 28q-4 1 -8 1h-192q-22 0 -28 -21q-1 -4 -1 -8v-324
q0 -22 21 -28q4 -1 8 -1h192z" />
<glyph glyph-name="e" unicode="e"
d="M396 0h-192q-57 0 -88 48q-18 27 -18 58v324q0 57 48 88q27 18 58 18h192q57 0 88 -48q18 -27 18 -58v-71q0 -57 -48 -89q-27 -17 -58 -17h-221v-147q0 -22 21 -28q4 -1 8 -1h192q22 0 28 21q1 4 1 8v38h77v-38q0 -57 -48 -88q-27 -18 -58 -18zM175 330h221q22 0 28 21
q1 4 1 8v71q0 22 -21 28q-4 1 -8 1h-192q-22 0 -28 -21q-1 -4 -1 -8v-100z" />
<glyph glyph-name="f" unicode="f"
d="M473 711q-72 0 -115 -57q-28 -39 -29 -86v-32h164v-77h-164v-382h164v-77h-164h-76h-164v77h164v382h-164v77h164v32q0 99 74 165q63 55 146 55h38v-77h-38z" />
<glyph glyph-name="g" unicode="g"
d="M175 536h357v-77h-63q4 -15 4 -30v-130q0 -57 -48 -89q-27 -17 -58 -17h-119q-40 -1 -61 -34h181q57 0 89 -48q17 -27 17 -59v-78q0 -57 -48 -89q-27 -17 -58 -17h-193q-58 0 -89 48q-17 27 -18 58v78q1 44 31 75q2 43 26 78q-49 26 -56 81q-1 7 -1 13v130q0 58 48 89
q28 18 59 18zM397 52q0 21 -20 28q-5 2 -9 2h-193q-21 0 -28 -20l-2 -10v-78q0 -21 20 -28l10 -2h193q21 0 28 20q1 6 1 10v78zM396 429q0 22 -20 28q-5 2 -9 2h-33h-159q-21 0 -28 -20l-2 -10v-130q0 -21 20 -28q5 -1 10 -1h73h119q21 0 27 20q2 5 2 9v130z" />
<glyph glyph-name="h" unicode="h"
d="M427 536q57 0 88 -48q17 -27 18 -58v-430h-77v430q0 22 -21 28q-5 1 -8 1h-215v-459h-77v459v77v146q0 22 -21 28q-4 1 -8 1h-38l-1 77h39q57 0 88 -48q17 -27 18 -58v-146h215z" />
<glyph glyph-name="i" unicode="i"
d="M338 607h-76v93h76v-93zM338 77h164v-77h-164h-76h-164v77h164v382h-164v77h240v-459z" />
<glyph glyph-name="j" unicode="j"
d="M98 -56h20q72 0 115 58q29 38 29 85v372h-164v77h404v-77h-164v-372q0 -99 -74 -164q-63 -55 -146 -55h-20v76zM338 700v-93h-76v93h76z" />
<glyph glyph-name="k" unicode="k"
d="M397 109q16 -31 48 -35h38v-77h-38q-65 0 -104 53q-11 16 -17 34l-66 196l-65 -62v-221h-76v791h76v-463l179 171h111l-164 -157z" />
<glyph glyph-name="l" unicode="l"
d="M326 682v-608h176v-77h-176h-77h-151v77h151v608q0 21 -20 28q-5 1 -9 1h-39v77h39q57 0 88 -48q17 -27 18 -58z" />
<glyph glyph-name="m" unicode="m"
d="M448 536q57 0 88 -48q17 -27 18 -58v-430h-77v430q0 22 -21 28q-5 1 -8 1h-47q-22 0 -28 -21q-1 -4 -1 -8v-430h-77v430q0 22 -21 28q-4 1 -8 1h-46q-22 0 -28 -21l-2 -8v-430h-76v430q0 22 -21 28q-5 1 -8 1h-39v77h39q38 -1 67 -25q30 24 68 25h46q39 -1 68 -25
q30 24 67 25h47z" />
<glyph glyph-name="n" unicode="n"
d="M432 536q57 0 89 -48q17 -27 17 -58v-430h-77v430q0 22 -21 28q-4 1 -8 1h-196q-22 0 -28 -21q-1 -4 -1 -8v-430h-76v430q0 22 -22 28q-4 1 -8 1h-38v77h38q39 -1 68 -25q30 24 67 25h196z" />
<glyph glyph-name="o" unicode="o"
d="M396 535q58 0 89 -48q17 -27 18 -58v-326q0 -57 -48 -88q-27 -18 -59 -18h-192q-58 0 -89 48q-17 27 -18 58v326q0 57 48 88q27 18 59 18h192zM426 103v326q0 21 -20 28q-5 1 -10 1h-192q-21 0 -28 -20q-2 -5 -2 -9v-326q0 -21 20 -28q5 -1 10 -1h192q21 0 28 20q2 5 2 9
z" />
<glyph glyph-name="p" unicode="p"
d="M537 104q0 -58 -48 -89q-27 -17 -59 -18h-193q-15 1 -29 4v-133h-77v561q0 22 -20 28q-5 2 -10 2h-38v77h39q38 -1 68 -25q30 24 67 25h193q58 0 89 -48q18 -27 18 -59v-325zM460 104v325q0 22 -20 28q-5 2 -10 2h-193q-21 0 -27 -20q-2 -5 -2 -10v-325q0 -21 20 -28
q5 -2 9 -2h193q22 0 28 20q2 5 2 10z" />
<glyph glyph-name="q" unicode="q"
d="M170 -3q-58 0 -89 48q-18 27 -18 59v325q0 58 48 89q27 18 59 18h193q38 -1 67 -25q31 24 68 25h39v-77h-38q-23 0 -29 -22q-1 -4 -1 -8v-561h-77v133q-15 -4 -29 -4h-193zM140 429v-325q0 -21 20 -28q5 -2 10 -2h193q21 0 27 20q2 5 2 10v325q0 22 -20 28q-5 2 -9 2
h-193q-22 0 -28 -20q-2 -5 -2 -10z" />
<glyph glyph-name="r" unicode="r"
d="M432 538q58 0 89 -48q17 -27 18 -58v-38h-77v38q0 21 -20 28q-5 1 -10 1h-196q-22 0 -28 -19q-2 -6 -2 -10v-432h-77v432q0 21 -20 28q-4 1 -9 1h-39v77h39q38 0 68 -24q30 24 68 24h196z" />
<glyph glyph-name="s" unicode="s"
d="M397 -3h-194q-57 0 -88 48q-18 27 -18 59v38h77v-38q0 -21 20 -28q5 -2 9 -2h194q21 0 27 20q2 5 2 10v87q0 21 -20 28q-5 1 -9 1h-194q-57 0 -88 49q-18 27 -18 58v102q0 58 48 89q27 18 58 18h194q57 0 88 -48q18 -27 18 -59h-77q0 22 -20 28q-5 2 -9 2h-194
q-21 0 -27 -20q-2 -5 -2 -10v-102q0 -21 20 -28q5 -2 9 -2h194q57 0 88 -48q18 -27 18 -58v-87q0 -58 -48 -89q-27 -17 -58 -18z" />
<glyph glyph-name="t" unicode="t"
d="M463 78h38v-77h-38q-102 0 -170 76q-57 66 -58 151v231h-136v77h136v136h77v-136h186v-77h-186v-231q0 -74 60 -120q41 -30 91 -30z" />
<glyph glyph-name="u" unicode="u"
d="M537 0h-39q-37 1 -66 23q-31 -25 -70 -26h-193q-57 0 -89 48q-17 27 -17 59v432h77v-432q0 -21 20 -28q5 -2 9 -2h193q21 0 28 20q2 5 2 10v2v430h77v-430q0 -21 20 -27q4 -2 9 -2h39v-77z" />
<glyph glyph-name="v" unicode="v"
d="M491 536l-140 -539h-102l-140 539h79l112 -431l112 431h79z" />
<glyph glyph-name="w" unicode="w"
d="M131 -3q-57 0 -88 48q-18 27 -18 59v432h77v-432q0 -21 20 -28q5 -2 9 -2h65q21 0 28 20q1 5 1 10v432h77v-432q0 -21 20 -28q5 -2 10 -2h68q21 0 28 20q2 5 2 10v2v430h77v-430q0 -21 20 -27q5 -2 9 -2h39v-77h-39q-37 1 -66 23q-31 -25 -70 -26h-68q-39 1 -68 25
q-30 -24 -68 -25h-65z" />
<glyph glyph-name="x" unicode="x"
d="M509 536l-164 -274l160 -266h-90l-115 192l-115 -192h-90l160 266l-164 274h90l119 -199l119 199h90z" />
<glyph glyph-name="y" unicode="y"
d="M442 536h82l-193 -518q-32 -88 -118 -128q-48 -22 -99 -22h-38v76h38q75 0 122 59q15 20 23 42l9 24l-129 372q-7 17 -25 18h-38v77h38q56 0 87 -47q7 -11 11 -22l98 -285z" />
<glyph glyph-name="z" unicode="z"
d="M503 -3h-406v58l303 404h-303v77h406v-67l-295 -395h295v-77zM135 42z" />
<glyph glyph-name="braceleft" unicode="{"
d="M429 0h-79q-66 0 -104 53q-24 34 -24 74v178q0 32 -29 46q-11 4 -22 5v76q32 0 46 30q5 10 5 21v177q0 66 54 104q33 24 74 24h79v-77h-79q-33 0 -47 -29q-4 -11 -5 -22v-177q0 -52 -36 -89q36 -38 36 -89v-178q0 -32 30 -46q11 -4 22 -5h79v-76z" />
<glyph glyph-name="bar" unicode="|"
d="M262 -56v844h76v-844h-76z" />
<glyph glyph-name="braceright" unicode="}"
d="M250 0h-79v76h79q33 0 47 30q4 11 5 21v178q0 52 36 89q-36 38 -36 89v177q0 33 -30 47q-11 4 -22 4h-79v77h79q66 0 104 -54q24 -33 24 -74v-177q0 -32 29 -46q11 -5 22 -5v-76q-32 0 -46 -30q-5 -11 -5 -21v-178q0 -65 -54 -103q-33 -24 -74 -24z" />
<glyph glyph-name="asciitilde" unicode="~"
d="M360 238q-32 0 -76 30q-31 20 -42 17q-8 -3 -19 -14l-27 -27l-54 54l27 27q66 66 148 13q4 -3 9 -7q30 -20 39 -16q5 3 12 10l27 27l54 -54l-27 -27q-34 -33 -71 -33z" />
<glyph glyph-name="uni007F"
/>
<glyph glyph-name="uni0080"
/>
<glyph glyph-name="uni0081"
/>
<glyph glyph-name="uni0082"
/>
<glyph glyph-name="uni0083"
/>
<glyph glyph-name="uni0084"
/>
<glyph glyph-name="uni0085" unicode="&#x85;"
/>
<glyph glyph-name="uni0086"
/>
<glyph glyph-name="uni0087"
/>
<glyph glyph-name="uni0088"
/>
<glyph glyph-name="uni0089"
/>
<glyph glyph-name="uni008A"
/>
<glyph glyph-name="uni008B"
/>
<glyph glyph-name="uni008C"
/>
<glyph glyph-name="uni008D"
/>
<glyph glyph-name="uni008E"
/>
<glyph glyph-name="uni008F"
/>
<glyph glyph-name="uni0090"
/>
<glyph glyph-name="uni0091"
/>
<glyph glyph-name="uni0092"
/>
<glyph glyph-name="uni0093"
/>
<glyph glyph-name="uni0094"
/>
<glyph glyph-name="uni0095"
/>
<glyph glyph-name="uni0096"
/>
<glyph glyph-name="uni0097"
/>
<glyph glyph-name="uni0098"
/>
<glyph glyph-name="uni0099"
/>
<glyph glyph-name="uni009A"
/>
<glyph glyph-name="uni009B"
/>
<glyph glyph-name="uni009C"
/>
<glyph glyph-name="uni009D"
/>
<glyph glyph-name="uni009E"
/>
<glyph glyph-name="uni009F"
/>
<glyph glyph-name="uni00A0" unicode="&#xa0;"
/>
<glyph glyph-name="exclamdown" unicode="&#xa1;"
/>
<glyph glyph-name="cent" unicode="&#xa2;"
/>
<glyph glyph-name="sterling" unicode="&#xa3;"
/>
<glyph glyph-name="currency" unicode="&#xa4;"
/>
<glyph glyph-name="yen" unicode="&#xa5;"
/>
<glyph glyph-name="brokenbar" unicode="&#xa6;"
/>
<glyph glyph-name="section" unicode="&#xa7;"
/>
<glyph glyph-name="dieresis" unicode="&#xa8;"
/>
<glyph glyph-name="copyright" unicode="&#xa9;"
/>
<glyph glyph-name="ordfeminine" unicode="&#xaa;"
/>
<glyph glyph-name="guillemotleft" unicode="&#xab;"
/>
<glyph glyph-name="logicalnot" unicode="&#xac;"
/>
<glyph glyph-name="uni00AD" unicode="&#xad;"
/>
<glyph glyph-name="registered" unicode="&#xae;"
/>
<glyph glyph-name="macron" unicode="&#xaf;"
/>
<glyph glyph-name="degree" unicode="&#xb0;"
/>
<glyph glyph-name="plusminus" unicode="&#xb1;"
/>
<glyph glyph-name="uni00B2" unicode="&#xb2;"
/>
<glyph glyph-name="uni00B3" unicode="&#xb3;"
/>
<glyph glyph-name="acute" unicode="&#xb4;"
/>
<glyph glyph-name="mu" unicode="&#xb5;"
/>
<glyph glyph-name="paragraph" unicode="&#xb6;"
/>
<glyph glyph-name="periodcentered" unicode="&#xb7;"
/>
<glyph glyph-name="cedilla" unicode="&#xb8;"
/>
<glyph glyph-name="uni00B9" unicode="&#xb9;"
/>
<glyph glyph-name="ordmasculine" unicode="&#xba;"
/>
<glyph glyph-name="guillemotright" unicode="&#xbb;"
/>
<glyph glyph-name="onequarter" unicode="&#xbc;"
/>
<glyph glyph-name="onehalf" unicode="&#xbd;"
/>
<glyph glyph-name="threequarters" unicode="&#xbe;"
/>
<glyph glyph-name="questiondown" unicode="&#xbf;"
/>
<glyph glyph-name="Agrave" unicode="&#xc0;"
/>
<glyph glyph-name="Aacute" unicode="&#xc1;"
/>
<glyph glyph-name="Acircumflex" unicode="&#xc2;"
/>
<glyph glyph-name="Atilde" unicode="&#xc3;"
/>
<glyph glyph-name="Adieresis" unicode="&#xc4;"
/>
<glyph glyph-name="Aring" unicode="&#xc5;"
/>
<glyph glyph-name="AE" unicode="&#xc6;"
/>
<glyph glyph-name="Ccedilla" unicode="&#xc7;"
/>
<glyph glyph-name="Egrave" unicode="&#xc8;"
/>
<glyph glyph-name="Eacute" unicode="&#xc9;"
/>
<glyph glyph-name="Ecircumflex" unicode="&#xca;"
/>
<glyph glyph-name="Edieresis" unicode="&#xcb;"
/>
<glyph glyph-name="Igrave" unicode="&#xcc;"
/>
<glyph glyph-name="Iacute" unicode="&#xcd;"
/>
<glyph glyph-name="Icircumflex" unicode="&#xce;"
/>
<glyph glyph-name="Idieresis" unicode="&#xcf;"
/>
<glyph glyph-name="Eth" unicode="&#xd0;"
/>
<glyph glyph-name="Ntilde" unicode="&#xd1;"
/>
<glyph glyph-name="Ograve" unicode="&#xd2;"
/>
<glyph glyph-name="Oacute" unicode="&#xd3;"
/>
<glyph glyph-name="Ocircumflex" unicode="&#xd4;"
/>
<glyph glyph-name="Otilde" unicode="&#xd5;"
/>
<glyph glyph-name="Odieresis" unicode="&#xd6;"
/>
<glyph glyph-name="multiply" unicode="&#xd7;"
/>
<glyph glyph-name="Oslash" unicode="&#xd8;"
/>
<glyph glyph-name="Ugrave" unicode="&#xd9;"
/>
<glyph glyph-name="Uacute" unicode="&#xda;"
/>
<glyph glyph-name="Ucircumflex" unicode="&#xdb;"
/>
<glyph glyph-name="Udieresis" unicode="&#xdc;"
/>
<glyph glyph-name="Yacute" unicode="&#xdd;"
/>
<glyph glyph-name="Thorn" unicode="&#xde;"
/>
<glyph glyph-name="germandbls" unicode="&#xdf;"
/>
<glyph glyph-name="agrave" unicode="&#xe0;"
/>
<glyph glyph-name="aacute" unicode="&#xe1;"
/>
<glyph glyph-name="acircumflex" unicode="&#xe2;"
/>
<glyph glyph-name="atilde" unicode="&#xe3;"
/>
<glyph glyph-name="adieresis" unicode="&#xe4;"
/>
<glyph glyph-name="aring" unicode="&#xe5;"
/>
<glyph glyph-name="ae" unicode="&#xe6;"
/>
<glyph glyph-name="ccedilla" unicode="&#xe7;"
/>
<glyph glyph-name="egrave" unicode="&#xe8;"
/>
<glyph glyph-name="eacute" unicode="&#xe9;"
/>
<glyph glyph-name="ecircumflex" unicode="&#xea;"
/>
<glyph glyph-name="edieresis" unicode="&#xeb;"
/>
<glyph glyph-name="igrave" unicode="&#xec;"
/>
<glyph glyph-name="iacute" unicode="&#xed;"
/>
<glyph glyph-name="icircumflex" unicode="&#xee;"
/>
<glyph glyph-name="idieresis" unicode="&#xef;"
/>
<glyph glyph-name="eth" unicode="&#xf0;"
/>
<glyph glyph-name="ntilde" unicode="&#xf1;"
/>
<glyph glyph-name="ograve" unicode="&#xf2;"
/>
<glyph glyph-name="oacute" unicode="&#xf3;"
/>
<glyph glyph-name="ocircumflex" unicode="&#xf4;"
/>
<glyph glyph-name="otilde" unicode="&#xf5;"
/>
<glyph glyph-name="odieresis" unicode="&#xf6;"
/>
<glyph glyph-name="divide" unicode="&#xf7;"
/>
<glyph glyph-name="oslash" unicode="&#xf8;"
/>
<glyph glyph-name="ugrave" unicode="&#xf9;"
/>
<glyph glyph-name="uacute" unicode="&#xfa;"
/>
<glyph glyph-name="ucircumflex" unicode="&#xfb;"
/>
<glyph glyph-name="udieresis" unicode="&#xfc;"
/>
<glyph glyph-name="yacute" unicode="&#xfd;"
/>
<glyph glyph-name="thorn" unicode="&#xfe;"
/>
<glyph glyph-name="ydieresis" unicode="&#xff;"
/>
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,65 @@
html, body {
position: relative;
width: 100%;
height: 100%;
}
body {
color: #333;
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
/* tmp? */
background: bisque;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
-webkit-padding: 0.4em 0;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:disabled {
color: #999;
}
button:not(:disabled):active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

View File

@@ -0,0 +1 @@
<svg id="visual" viewBox="0 0 900 150" width="900" height="150" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><path d="M0 36L21.5 33.7C43 31.3 86 26.7 128.8 32.5C171.7 38.3 214.3 54.7 257.2 63.5C300 72.3 343 73.7 385.8 65.7C428.7 57.7 471.3 40.3 514.2 30.5C557 20.7 600 18.3 642.8 25.8C685.7 33.3 728.3 50.7 771.2 59.5C814 68.3 857 68.7 878.5 68.8L900 69L900 0L878.5 0C857 0 814 0 771.2 0C728.3 0 685.7 0 642.8 0C600 0 557 0 514.2 0C471.3 0 428.7 0 385.8 0C343 0 300 0 257.2 0C214.3 0 171.7 0 128.8 0C86 0 43 0 21.5 0L0 0Z" fill="#618174" stroke-linecap="round" stroke-linejoin="miter"></path></svg>

After

Width:  |  Height:  |  Size: 645 B

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/bundle.css'>
<script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,83 @@
import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';
import css from 'rollup-plugin-css-only';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
export default {
input: 'src/main.ts',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
preprocess: sveltePreprocess({ sourceMap: !production }),
compilerOptions: {
// enable run-time checks when not in production
dev: !production
}
}),
// we'll extract any component CSS out into
// a separate file - better for performance
css({ output: 'bundle.css' }),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
typescript({
sourceMap: !production,
inlineSources: !production
}),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};

View File

@@ -0,0 +1,36 @@
<script lang="ts">
// routing
// may not need {link} here
import Router, { link } from "svelte-spa-router";
import { primaryRoutes } from "./routes/primaryRoutes.js";
// this page should handle the SPA history management...
// set to false later for actual security
// let loggedIn = true;
// $: logout = async() => {
// await fetch("http://transcendance:8080/api/v2/auth/logout",{
// method : 'POST',
// }).then(push('/login'));
// }
</script>
<!-- <h1>Testing</h1> -->
<Router routes={primaryRoutes} />
<style>
/* doesn't work... */
/* body{
background: bisque;
} */
</style>

View File

@@ -0,0 +1,17 @@
<script>
import { link } from "svelte-spa-router";
</script>
<h1>We are sorry!</h1>
<p>This isn't a url that we use.</p>
<!-- <img src="https://picsum.photos/id/685/800/400" alt="img"> -->
<p>Go home you're drunk.</p>
<a href="/" use:link>
<h2>Take me home →</h2>
</a>
<style>
/* img {
width: 100%;
} */
</style>

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import {location} from 'svelte-spa-router';
// this could be how i
// this is how you access /:first for example
// export let params = {}
// <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
// If i export these vars, maybe as an nice tidy object, i could pass whatever i like to them
// The current user, some other user, whatever, and thus reuse this Componente for the user and their friends or whatever
// will have to coordinate with Back, will know more once the Game stats are in the back
// wait maybe this won't work, cuz like it's still going through a route, i would have to update a Store Var each time...
// not sure if that's what i want...
// All the variables that will eventually be replaced by the real values
let username = 'Username';
let games = { total: 7, won: 4, lost: 3};
let rank = 'gold or whatever the fuck who cares...';
export const user = {
username: 'chaboi',
}
// maybe the rank is determined dynamically just in the front based on win loss ratio or something no one cares about
// why bother storing that shit in the back...
// maybe i need a Rank.svelte component
// ohhh i could make above a certain rank glitter! like that CSS tutorial showed me!
</script>
<!-- i don't think i need a div around this anymore, do i? -->
<main>
<!-- what the fuck do we even want in here? messges, about the user, STATISTICS!!! -->
<!-- <div>some stuff goes here</div> -->
<img class="icon" src="img/default_user_icon.png" alt="default user icon">
<div>{username}</div>
<div>Rank: {rank}</div>
<section class="main-stats">
<h4>Match Statistics</h4>
<p>Total: {games.total}</p>
<p>Victories: {games.won}</p>
<p>Losses: {games.lost}</p>
</section>
</main>
<style>
/* very clearly redo all this */
div.main-grid{
display: grid;
grid-template-columns: repeat(12, 1fr);
/* max-height: calc(100vh - 30vh); */
height: 85vh;
}
section.sidebar{
grid-column: 1 / span 2;
background: white;
}
/* The main part */
main{
max-width: 960px;
margin: 40px auto;
text-align: center;
}
main.offset{
grid-column: 3 / span 10;
}
/* Normal CSS stuff */
.icon{
max-width: 150px;
}
/* The variable rich section */
section.main-stats{
max-width: 600px;
margin: 40px auto;
text-align: center;
/* i think i want to use a grid? */
display: grid;
grid-template-columns: repeat(3, 1fr);
/* not sure about this, maybe top should be larger? */
grid-template-rows: repeat(3, 1fr);
}
/* the stuff in the grid*/
section.main-stats h4{
grid-column: 1 / span 3;
}
</style>

View File

@@ -0,0 +1,48 @@
<script lang="ts">
// import { loginStatus } from "./stores/loginStatusStore.js";
import Header from "./components/Header.svelte";
import Footer from "./components/Footer.svelte";
// import { createEventDispatcher } from "svelte";
import { push } from "svelte-spa-router";
import Router, { link } from "svelte-spa-router";
import { profileRoutes, prefix } from "./routes/profileRoutes.js";
// let dispatch = createEventDispatcher();
</script>
<!-- remove ={clickedHome} if you want to forward the event to App.svelte-->
<Header />
<!-- The Wave -->
<!-- <div class="spacer layer1"></div> -->
<Router routes={profileRoutes} {prefix} />
<!-- <Footer /> -->
<style>
/* from Haikei */
/* for any Haikei image */
/* .spacer{
aspect-ratio: 900/300;
width: 100%;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
} */
/* the specific image we use, you need both classes */
/* .layer1{
background-image: url('/img/wave-haikei.svg');
} */
</style>

View File

@@ -0,0 +1,14 @@
<script lang="ts">
</script>
<h1>this is settings</h1>
<style>
</style>

View File

@@ -0,0 +1,190 @@
<script lang="ts">
import Canvas from "./components/Canvas.svelte";
import { createEventDispatcher } from "svelte";
import { push } from "svelte-spa-router";
// import axios from 'axios';
import { onMount } from 'svelte';
import { loginStatus } from './stores/loginStatusStore.js';
import { onDestroy } from 'svelte';
let dispatch = createEventDispatcher();
onMount(async () => {
// console.log('PROFIL SVELTE');
console.log('SplashPage testing if logged in')
// const {data} = await axios.get('http://transcendance:8080/api/v2/user');
// if (data) {
// $loginStatus = true;
// push('/user');
// }
});
const login = () => {
// document.body.scrollIntoView();
// push
window.location.href = 'http://transcendance:8080/api/v2/auth';
// await fetch ('http://transcendance:8080/api/v2/auth');
console.log('you are now logged in');
push('/profile');
// it doesn't wait before changing the page tho which is really annoying... maybe the backend needs to be updated idk
// cuz rn i'm doing it in the front and that doesn't seem great...
}
const logout = async() => {
await fetch('http://transcendance:8080/api/v2/auth/logout',);
$loginStatus = false;
};
</script>
<header class="grid-container">
<!-- <div on:mouseenter={enter} on:mouseleave={leave} class:active > -->
<h1>Potato Pong</h1>
<!-- </div> -->
<nav>
<!-- placeholder links -->
<a href="/">Somewhere</a>
<!-- <a href="/">SomewhereElse</a> -->
{#if $loginStatus == false}
<div on:click={login}>Login {$loginStatus}</div>
{:else}
<div on:click={logout}>Logout {$loginStatus}</div>
{/if}
<!-- one of these will be login and it will scroll you down to the login part -->
</nav>
<h2>
<div>Welcome to</div>
<div>Potato Pong</div>
</h2>
<!-- here i want a flashing arrow pointing down to the login part -->
</header>
<!-- <Canvas class=".canvas2"/> -->
<Canvas/>
<style>
/* currently useless */
/* No styles get applied to the canvas from here, maybe i should move them to the Canvas Component... */
/* tho tbh, why bother, i'm gonna change it anyway... */
.canvas{
/* grid-column: 1 / 13;
grid-row: 1 / 3; */
/* don't rely on Z-Index!!!! */
z-index: -1;
position: relative;
width: 100%;
height: 100%;
white-space: nowrap;
/* Tmp? */
/* background-color: #666; */
/* somehow this got rid of they annoying white space under the canvas */
padding-bottom: 0;
margin-bottom: 0px;
overflow: hidden;
}
.canvas2{
z-index: -1;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
white-space: nowrap;
/* Tmp? */
/* background-color: #666; */
/* somehow this got rid of they annoying white space under the canvas */
padding-bottom: 0;
margin-bottom: 0px;
overflow: hidden;
}
/* .canvas .grid-container{ */
.grid-container{
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
width: 100%;
height: 100%;
white-space: nowrap;
/* padding-bottom: 0; */
margin-bottom: 0px;
overflow: hidden;
padding: 20px 40px;
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
align-items: center;
}
header h1, header nav a{
/* tmp ? well i kinda like it */
color: bisque;
}
header h1{
grid-column: 1 / 7;
grid-row: 1;
/* grid-column: span 6; */
/* tmp? */
padding: 20px;
border: 1px solid bisque;
}
header nav{
/* make it a flexbox? */
grid-column: 7 / 13;
grid-row: 1;
justify-self: end;
/* tmp? */
padding: 20px;
border: 1px solid bisque;
}
header nav a{
margin-left: 10px;
text-decoration: none;
}
/* testing */
header nav a:hover{
font-weight: bold;
background-color: blue;
}
header h2:hover{
background: blue;
}
header h2{
grid-row: 3;
grid-column: 5 / span 4;
justify-self: center;
/* tmp */
border: 1px solid black;
z-index: 3;
}
header h2 div{
font-size: 2em;
}
/* tmp prolly */
nav div {
display: inline;
color: bisque;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,12 @@
<script>
import { onMount } from "svelte";
onMount( () => console.log('in TMP TEST'))
</script>
<h1>TMP TEST</h1>

View File

@@ -0,0 +1,112 @@
<script lang="ts">
import { onMount } from 'svelte';
// rename to PotatoCanvas?
let canvas;
// let img;
let loaded = false;
// i feel like they told me not to do this, new Image(), isn't there another way?
// never mind they do seem to do this shit...
// no idea if this should be in onMount...
const img = new Image();
img.src = 'img/potato_logo.png';
img.onload = () => {
loaded = true;
// not sure if i'll use this...
}
// $: scaleRatio = window.innerWidth / 10;
$: scaleRatio = 30;
$: nPotoatoRows = Math.floor(window.innerHeight / 200);
$: nPotoatoCols = Math.floor(window.innerWidth / 200);
// $: spacing = (canvas.height - (img.height / scaleRatio * (nPotoatoRows - 1) )) / nPotoatoRows;
// apparently i might need to move all my reactive statements outside the onMount... not sure why...
onMount(() => {
// in this on mount we will set the canvas and the img
const ctx = canvas.getContext('2d');
// let frame = requestAnimationFrame(loop);
ctx.width = window.innerWidth;
ctx.height = window.innerHeight;
// console.log(nPotoatoCols);
// img = fetch('img/potato_logo.png');
// Moving the IMG stuff from her out of mount
// let nPotoatoRows = 4;
// $: nPotoatoRows = Math.floor(canvas.height / 200);
let spacing = (canvas.height - (img.height / scaleRatio * (nPotoatoRows - 1) )) / nPotoatoRows;
// $: spacing = (canvas.height - (img.height / scaleRatio * (nPotoatoRows - 1) )) / nPotoatoRows;
// i prolly need a number of potatos X and Y vars
// i could do it so there are more potatos than can fit on the screen, translate them over, and then after some point
// shift them all back to the next position they would be in, so it looks like they keep going to infinity
// let dx = 1.5;
let dx = 1;
// let dy = -0.5;
let dy = -1;
// let startX = spacing;
// let startY = spacing;
// let startX = 0;
// let startY = 0;
let startX = -(spacing * 2.5);
let startY = -(spacing * 2.5);
let x = startX;
let y = startY;
let frame = requestAnimationFrame(loop);
// i don't think i need t...
function loop(t) {
// yea ok a loop in a loop in a loop, horrible idea...
ctx.clearRect(0, 0, canvas.width, canvas.height);
// this has to be at the end, no?
frame = requestAnimationFrame(loop);
// if (x > 900) {
// x = startX;
// y = startY;
// }
// else {
// x += dx;
// y += dy;
// }
for (let i = 0; i < 20; i++) {
for (let j = 0; j < 10; j++) {
// ctx.drawImage(img, x + (i * spacing * 2), y + (j * spacing), img.width / scaleRatio, img.height / scaleRatio);
ctx.drawImage(img, x + (i * spacing), y + (j * spacing), img.width / scaleRatio, img.height / scaleRatio);
}
}
}
return () => {
cancelAnimationFrame(frame);
};
});
</script>
<canvas
bind:this={canvas}
width={window.innerWidth}
height={window.innerHeight}
></canvas>
<style>
canvas {
width: 100%;
height: 100%;
background-color: #666;
}
</style>

Some files were not shown because too many files have changed in this diff Show More