Merge branch 'dev'

This commit is contained in:
Alexis
2020-05-23 01:04:05 +02:00
44 changed files with 15430 additions and 0 deletions

23
.gitignore vendored
View File

@@ -1 +1,24 @@
.DS_Store
node_modules node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# creds
/database/credentials.json

7
client/api/api.js Normal file
View File

@@ -0,0 +1,7 @@
import axios from "axios"
const url = "http://localhost:2814/api"
export default axios.create({
baseURL: url
})

View File

@@ -0,0 +1,11 @@
import spellsRepository from './spellsRepository'
// List of possible repositories
const repositories = {
spells: spellsRepository
}
// Usage : RepositoryFactoryInstance.get('reponame');
export const RepositoryFactory = {
get: name => repositories[name]
}

View File

@@ -0,0 +1,14 @@
import api from './api'
// URL for spells
const resource = "/spells"
// CRUD methods for spells
export default {
getSpells() {
return api.get(`${resource}`)
},
getSpell(id) {
return api.get(`${resource}?id=${id}`)
},
}

5
client/babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
}

12594
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

64
client/package.json Normal file
View File

@@ -0,0 +1,64 @@
{
"name": "spellsaurus_client",
"version": "1.0.0",
"description": "The client interface of spellsaurus",
"keywords": [
"rpg",
"dev",
"spells",
"roleplay",
"website",
"database"
],
"author": "AlexisNP",
"license": "ISC",
"bugs": {
"url": "https://github.com/AlexisNP/spellsaurus/issues"
},
"homepage": "https://github.com/AlexisNP/spellsaurus#readme",
"private": true,
"repository": {
"type": "git",
"url": "git+https://github.com/AlexisNP/spellsaurus.git"
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.19.2",
"core-js": "^3.6.4",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.2.0",
"@vue/cli-plugin-eslint": "~4.2.0",
"@vue/cli-service": "~4.2.0",
"babel-eslint": "^10.0.3",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.1.2",
"node-sass": "^4.13.1",
"sass-loader": "^8.0.2",
"vue-router": "^3.1.6",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

BIN
client/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
client/public/index.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Spellsaurus</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="srs"></div>
<!-- built files will be auto injected -->
</body>
</html>

28
client/src/app.vue Normal file
View File

@@ -0,0 +1,28 @@
<template>
<div id="srs">
<header>
<navigation/>
</header>
<router-view/>
</div>
</template>
<script>
import navbar from "./components/global/navbar/navbar";
export default {
name: 'app',
components: {
'navigation': navbar
}
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 125" style="enable-background:new 0 0 100 125;" xml:space="preserve">
<path d="M93,49.1c-2.4-0.5-4.5-1.5-5.1-4.4c-0.5-2.9,1.8-4,4.1-5.3c0.7-0.4,1.2-0.8,1.4-1.2c-0.2-0.7-0.4-1.4-0.6-2.1
c-0.5-0.3-1.1-0.5-2.1-0.4c-2.5,0.3-4.8,0-6.2-2.6c-1.4-2.6,0.5-4.3,2.2-6.3c0.5-0.6,0.8-1.1,0.9-1.4c-0.4-0.6-0.9-1.3-1.3-1.9
c-0.5-0.1-1.2,0-2,0.3c-2.3,1-4.6,1.5-6.7-0.6c-2.1-2-0.9-4.3,0.2-6.7c0.3-0.6,0.4-1.2,0.4-1.5c-0.6-0.5-1.3-1-2-1.5
c-0.5,0.1-1,0.4-1.6,0.9c-1.8,1.7-3.9,2.8-6.5,1.5s-2.2-3.8-1.9-6.4c0.1-0.7,0-1.2,0-1.5c-0.8-0.3-1.6-0.6-2.4-0.9
c-0.4,0.2-0.8,0.7-1.2,1.3c-1.2,2.1-2.8,3.9-5.7,3.5S53.7,9,53.1,6.5c-0.1-0.6-0.3-1.1-0.5-1.4C51.8,5,50.9,5,50,5l0,0
c-0.3,0.4-0.6,0.9-0.7,1.6c-0.5,2.4-1.5,4.5-4.4,5.1c-2.9,0.5-4-1.8-5.3-4.1c-0.3-0.5-0.6-0.9-0.9-1.2c-0.9,0.2-1.7,0.5-2.5,0.7
c-0.2,0.4-0.3,1-0.2,1.7c0.3,2.5,0,4.8-2.6,6.2s-4.3-0.5-6.3-2.2c-0.5-0.4-0.9-0.7-1.2-0.8c-0.7,0.5-1.5,1-2.2,1.5
c0,0.5,0.1,1,0.4,1.7c1,2.3,1.5,4.6-0.6,6.7c-2,2.1-4.3,0.9-6.7-0.2c-0.6-0.3-1.1-0.4-1.4-0.4c-0.6,0.7-1.1,1.3-1.6,2
c0.1,0.5,0.4,1,0.9,1.5c1.7,1.8,2.8,3.9,1.5,6.5s-3.8,2.2-6.4,1.9c-0.7-0.1-1.2,0-1.6,0c-0.3,0.8-0.6,1.5-0.9,2.3
C7.6,36.1,8,36.6,8.7,37c2.1,1.2,3.9,2.8,3.5,5.7s-2.9,3.2-5.5,3.8c-0.7,0.2-1.2,0.4-1.6,0.6C5,47.9,5,48.7,5,49.5
c0.4,0.4,0.9,0.7,1.8,0.9c2.4,0.5,4.5,1.5,5.1,4.4c0.5,2.9-1.8,4-4.1,5.3c-0.7,0.4-1.1,0.8-1.4,1.1c0.2,0.7,0.4,1.4,0.6,2.1
c0.5,0.3,1.1,0.5,2.1,0.3c2.5-0.3,4.8,0,6.2,2.6s-0.5,4.3-2.2,6.3c-0.6,0.7-0.9,1.2-1,1.6c0.4,0.6,0.7,1.1,1.1,1.7
c0.5,0.1,1.3,0.1,2.2-0.3c2.3-1,4.6-1.5,6.7,0.6c2.1,2,0.9,4.3-0.2,6.7c-0.4,0.9-0.5,1.5-0.5,2c0.5,0.4,1,0.8,1.5,1.1
c0.6,0,1.3-0.2,2.1-1c1.8-1.7,3.9-2.8,6.5-1.5s2.2,3.8,1.9,6.4c-0.1,1.1,0,1.7,0.2,2.1c0.5,0.2,1,0.4,1.6,0.6
c0.6-0.1,1.3-0.6,1.9-1.6c1.2-2.1,2.8-3.9,5.7-3.5s3.2,2.9,3.8,5.5c0.2,1.1,0.6,1.7,0.9,2c0.5,0,1,0,1.6,0.1
c0.5-0.3,1.1-0.9,1.3-2.1c0.5-2.4,1.5-4.5,4.4-5.1c2.9-0.5,4,1.8,5.3,4.1c0.6,1,1.1,1.4,1.5,1.6c0.5-0.1,1-0.3,1.4-0.4
c0.4-0.4,0.8-1.2,0.7-2.5c-0.3-2.5,0-4.8,2.6-6.2c2.6-1.4,4.3,0.5,6.3,2.2c0.8,0.8,1.5,1,1.9,1.1c0.4-0.3,0.8-0.6,1.3-0.9
c0.2-0.5,0.4-1.4-0.2-2.5c-1-2.3-1.5-4.6,0.6-6.7c2-2.1,4.3-0.9,6.7,0.2c1,0.4,1.7,0.5,2.1,0.4c0.3-0.4,0.7-0.9,1-1.3
c0-0.6-0.1-1.4-1-2.3c-1.7-1.8-2.8-3.9-1.5-6.5s3.8-2.2,6.4-1.9c1,0.1,1.7,0,2.1-0.2c0.2-0.6,0.4-1.1,0.6-1.7
c-0.2-0.6-0.6-1.2-1.6-1.8c-2.1-1.2-3.9-2.8-3.5-5.7s2.9-3.2,5.5-3.8c0.9-0.2,1.5-0.5,1.8-0.7c0-0.7,0-1.3,0.1-2
C94.7,49.8,94.1,49.3,93,49.1z M27.1,68.5c-1.8,0.3-4.1,0.1-5.6-2.7c-2.7-4.5-4.6-12.7-4.1-15.4s1.9-3.7,4.3-4.1s4.1,1.9,4.6,5.5
c0.5,3.4,1.7,7.7,3.3,10.5C31.1,65.1,28.9,68.2,27.1,68.5z M25.2,33.7c-0.8-1.6-1.4-3.8,0.8-6.2c3.4-3.9,10.7-8.3,13.4-8.6
c2.7-0.3,4.2,0.6,5.2,2.8c1.1,2.1-0.6,4.5-3.8,6c-3.1,1.5-6.8,4-9,6.4C29.7,36.4,26.1,35.3,25.2,33.7z M60.7,77.5
c-0.3,1.8-1.1,3.9-4.3,4.5c-5.1,1.2-13.5,0.4-15.9-0.9s-3-3-2.6-5.3c0.4-2.4,3.1-3.3,6.6-2.6c3.4,0.6,7.8,0.8,11,0.1
C58.8,72.7,61,75.7,60.7,77.5z M50,68c-9.9,0-18-8.1-18-18s8.1-18,18-18s18,8.1,18,18S59.9,68,50,68z M68.3,34.2
c-2.4-2.5-5.9-5.2-8.8-6.6c-2.9-1.4-3-5.2-1.7-6.5c1.3-1.3,3.2-2.5,6.1-1.1c4.8,2.1,11.2,7.6,12.3,10.1c1.2,2.5,0.7,4.1-1,5.8
C73.6,37.6,70.8,36.8,68.3,34.2z M82.7,53.8c-0.5,5.2-3.8,13-5.8,14.9c-2,1.9-3.7,1.9-5.9,0.8c-2.1-1.1-2.2-4-0.5-7.1
c1.6-3,3.1-7.2,3.5-10.4s4-4.4,5.6-3.6C81.4,49.1,83.1,50.6,82.7,53.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 125" style="enable-background:new 0 0 100 100;" xml:space="preserve"><path d="M98,53.9v-7.6l-8.3-2c-0.2-1.5-0.4-2.9-0.7-4.3l7.1-5.1l-2.9-7.1l-8.6,1.5c-0.7-1.2-1.5-2.3-2.3-3.4l4.6-7.4 l-5.4-5.4l-7.4,4.6c-1.2-0.9-2.4-1.7-3.6-2.4l1.4-8.6l-7.1-2.9l-5,7.1c-1.3-0.3-2.7-0.4-4.1-0.6l-2-8.3h-7.6l-2,8.3 c-1.5,0.2-2.9,0.4-4.3,0.7l-5.1-7.1l-7.1,2.9l1.5,8.6c-1.2,0.7-2.3,1.5-3.4,2.3l-7.4-4.6l-5.4,5.4l4.6,7.4c-0.9,1.2-1.7,2.4-2.4,3.6 l-8.6-1.4l-2.9,7.1l7.1,5c-0.3,1.3-0.8,2.7-1,4.1l-8.7,2v7.6l8.7,2c0.2,1.5,0.6,2.9,1,4.3l-7,5.1l3,7.1l8.6-1.5 c0.7,1.2,1.5,2.3,2.3,3.4l-4.6,7.4l5.4,5.4l7.4-4.6c1.2,0.9,2.4,1.7,3.6,2.4L28,93.5l7.1,2.9l5-7.1c1.3,0.3,2.7,0.8,4.1,1l2,8.7h7.6 l2-8.7c1.5-0.2,2.9-0.6,4.3-1l5.1,7l7.1-3l-1.5-8.6c1.2-0.7,2.3-1.5,3.4-2.3l7.4,4.6l5.4-5.4l-4.6-7.4c0.9-1.2,1.7-2.4,2.4-3.6 l8.6,1.4l2.9-7.1l-7.1-5c0.3-1.3,0.4-2.7,0.6-4.1L98,53.9z M73.5,55.2c-0.6,3.2-1.9,6.2-3.7,8.9c1.8,1.9,1.8,4.8,0,6.7 c-1.8,1.8-4.8,1.9-6.7,0c-2.6,1.8-5.7,3.1-8.9,3.7c0,2.6-2.1,4.7-4.7,4.7s-4.7-2.1-4.7-4.7c-3.2-0.6-6.2-1.9-8.9-3.7 c-1.9,1.8-4.8,1.8-6.7,0c-1.8-1.8-1.9-4.8,0-6.7c-1.8-2.6-3.1-5.7-3.7-8.9c-2.6,0-4.7-2.1-4.7-4.7s2.1-4.7,4.7-4.7 c0.6-3.2,1.9-6.2,3.7-8.9c-1.8-1.9-1.8-4.8,0-6.7c1.8-1.8,4.8-1.9,6.7,0c2.6-1.8,5.7-3.1,8.9-3.7c0-2.6,2.1-4.7,4.7-4.7 s4.7,2.1,4.7,4.7c3.2,0.6,6.2,1.9,8.9,3.7c1.9-1.8,4.8-1.8,6.7,0c1.8,1.8,1.9,4.8,0,6.7c1.8,2.6,3.1,5.7,3.7,8.9 c2.6,0,4.7,2.1,4.7,4.7S76.1,55.2,73.5,55.2z"/><path d="M68.8,50.5c0-2.3,1.6-4.2,3.8-4.7c-0.6-3-1.8-5.9-3.5-8.3c-1.8,1.2-4.3,1-6-0.6c-1.6-1.6-1.8-4.1-0.6-6 c-2.5-1.7-5.3-2.9-8.3-3.5c-0.4,2.2-2.4,3.8-4.7,3.8s-4.2-1.6-4.7-3.8c-3,0.6-5.9,1.8-8.3,3.5c1.2,1.8,1,4.3-0.6,6 c-1.6,1.6-4.1,1.8-6,0.6c-1.7,2.5-2.9,5.3-3.5,8.3c2.2,0.4,3.8,2.4,3.8,4.7s-1.6,4.2-3.8,4.7c0.6,3,1.8,5.9,3.5,8.3 c1.8-1.2,4.3-1,6,0.6c1.6,1.6,1.8,4.1,0.6,6c2.5,1.7,5.3,2.9,8.3,3.5c0.4-2.2,2.4-3.8,4.7-3.8s4.2,1.6,4.7,3.8 c3-0.6,5.9-1.8,8.3-3.5c-1.2-1.8-1-4.3,0.6-6c1.6-1.6,4.1-1.8,6-0.6c1.7-2.5,2.9-5.3,3.5-8.3C70.4,54.7,68.8,52.8,68.8,50.5z M49.5,62.2c-6.8,0-12.2-5.5-12.2-12.2s5.5-12.2,12.2-12.2S61.8,43.2,61.8,50S56.3,62.2,49.5,62.2z"/><text x="0" y="115" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by jayson lim</text><text x="0" y="120" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 125" version="1.1" xml:space="preserve" style="" x="0px" y="0px" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.41421"><g transform="matrix(1.9721,0,0,1.9952,-72.6849,-77.2885)"><path d="M68.801,43.391C64.516,42.007 59.904,42.007 55.62,43.391L57.024,47.738C56.163,48.016 55.327,48.363 54.522,48.775L52.441,44.707C48.432,46.759 45.171,50.02 43.12,54.028L47.188,56.109C46.776,56.914 46.429,57.751 46.151,58.611L41.803,57.207C40.42,61.491 40.42,66.103 41.803,70.388L46.151,68.984C46.429,69.844 46.776,70.68 47.188,71.485L43.12,73.567C45.171,77.575 48.432,80.836 52.441,82.887L54.522,78.82C55.327,79.232 56.163,79.578 57.024,79.856L55.62,84.204C59.904,85.588 64.516,85.588 68.801,84.204L67.396,79.856C68.257,79.578 69.093,79.232 69.898,78.82L71.98,82.887C75.988,80.836 79.249,77.575 81.3,73.567L77.233,71.485C77.645,70.68 77.991,69.844 78.269,68.984L82.617,70.388C84.001,66.103 84.001,61.491 82.617,57.207L78.269,58.611C77.991,57.751 77.645,56.914 77.233,56.109L81.3,54.028C79.249,50.02 75.988,46.759 71.98,44.707L69.898,48.775C69.093,48.363 68.257,48.016 67.396,47.738L68.801,43.391ZM62.21,57.799C65.521,57.799 68.209,60.487 68.209,63.797C68.209,67.108 65.521,69.796 62.21,69.796C58.899,69.796 56.212,67.108 56.212,63.797C56.212,60.487 58.899,57.799 62.21,57.799Z" style="" fill="black"/></g><text x="0" y="115" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Veronika Krpciarova</text><text x="0" y="120" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 125" style="enable-background:new 0 0 100 100;" xml:space="preserve"><switch><foreignObject requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/" x="0" y="0" width="1" height="1"/><g i:extraneous="self"><path d="M97.5,52.7v-5.4l-6.2-0.6c0-0.2,0-0.4,0-0.7l6-1.7l-0.9-5.3l-6.3,0.5c-0.1-0.2-0.1-0.4-0.2-0.6l5.7-2.7l-1.8-5.1l-6.1,1.6 c-0.1-0.2-0.2-0.4-0.3-0.6l5.1-3.6l-2.7-4.7l-5.7,2.6c-0.1-0.2-0.3-0.3-0.4-0.5l4.4-4.5l-3.5-4.1L79.5,21 c-0.2-0.2-0.3-0.3-0.5-0.5l3.6-5.2l-4.1-3.5L74,16.3c-0.2-0.1-0.3-0.3-0.5-0.4l2.6-5.7l-4.7-2.7l-3.6,5.1 c-0.2-0.1-0.4-0.2-0.6-0.3l1.6-6.1l-5.1-1.8L61,10.1c-0.2-0.1-0.4-0.1-0.6-0.2l0.5-6.3l-5.3-0.9l-1.7,6c-0.2,0-0.4,0-0.7,0 l-0.6-6.2h-5.4l-0.6,6.2c-0.2,0-0.4,0-0.7,0l-1.7-6l-5.3,0.9l0.5,6.3c-0.2,0.1-0.4,0.1-0.6,0.2l-2.7-5.7l-5.1,1.8l1.6,6.1 c-0.2,0.1-0.4,0.2-0.6,0.3l-3.6-5.1l-4.7,2.7l2.6,5.7c-0.2,0.1-0.3,0.3-0.5,0.4l-4.5-4.4l-4.1,3.5l3.6,5.2 c-0.2,0.2-0.3,0.3-0.5,0.5l-5.2-3.6l-3.5,4.1l4.4,4.5c-0.1,0.2-0.3,0.3-0.4,0.5l-5.7-2.6l-2.7,4.7l5.1,3.6 c-0.1,0.2-0.2,0.4-0.3,0.6l-6.1-1.6l-1.8,5.1l5.7,2.7c-0.1,0.2-0.1,0.4-0.2,0.6l-6.3-0.5l-0.9,5.3l6,1.7c0,0.2,0,0.4,0,0.7 l-6.2,0.6v5.4l6.2,0.6c0,0.2,0,0.4,0,0.7l-6,1.7l0.9,5.3l6.3-0.5c0.1,0.2,0.1,0.4,0.2,0.6l-5.7,2.7l1.8,5.1l6.1-1.6 c0.1,0.2,0.2,0.4,0.3,0.6l-5.1,3.6l2.7,4.7l5.7-2.6c0.1,0.2,0.3,0.3,0.4,0.5l-4.4,4.5l3.5,4.1l5.2-3.6c0.2,0.2,0.3,0.3,0.5,0.5 l-3.6,5.2l4.1,3.5l4.5-4.4c0.2,0.1,0.3,0.3,0.5,0.4l-2.6,5.7l4.7,2.7l3.6-5.1c0.2,0.1,0.4,0.2,0.6,0.3l-1.6,6.1l5.1,1.8l2.7-5.7 c0.2,0.1,0.4,0.1,0.6,0.2l-0.5,6.3l5.3,0.9l1.7-6c0.2,0,0.4,0,0.7,0l0.6,6.2h5.4l0.6-6.2c0.2,0,0.4,0,0.7,0l1.7,6l5.3-0.9 l-0.5-6.3c0.2-0.1,0.4-0.1,0.6-0.2l2.7,5.7l5.1-1.8l-1.6-6.1c0.2-0.1,0.4-0.2,0.6-0.3l3.6,5.1l4.7-2.7l-2.6-5.7 c0.2-0.1,0.3-0.3,0.5-0.4l4.5,4.4l4.1-3.5L79,79.5c0.2-0.2,0.3-0.3,0.5-0.5l5.2,3.6l3.5-4.1L83.7,74c0.1-0.2,0.3-0.3,0.4-0.5 l5.7,2.6l2.7-4.7l-5.1-3.6c0.1-0.2,0.2-0.4,0.3-0.6l6.1,1.6l1.8-5.1L89.9,61c0.1-0.2,0.1-0.4,0.2-0.6l6.3,0.5l0.9-5.3l-6-1.7 c0-0.2,0-0.4,0-0.7L97.5,52.7z M50,57.8c-4.3,0-7.8-3.5-7.8-7.8s3.5-7.8,7.8-7.8s7.8,3.5,7.8,7.8S54.3,57.8,50,57.8z M53.4,35.9 V15.7c10.2,1,19,6.3,24.7,14.2L60.5,40C58.6,38,56.2,36.5,53.4,35.9z M46.6,35.9c-2.8,0.7-5.3,2.1-7.2,4.1L21.9,29.9 c5.7-7.9,14.5-13.2,24.7-14.2V35.9z M18.5,35.7l17.6,10.1c-0.4,1.3-0.7,2.7-0.7,4.2c0,1.4,0.3,2.8,0.7,4.1L18.5,64.3 c-2-4.4-3.2-9.2-3.2-14.3C15.3,44.9,16.5,40.1,18.5,35.7z M39.5,60c1.9,2,4.4,3.5,7.2,4.1v20.2c-10.2-1-19.1-6.3-24.7-14.2 L39.5,60z M53.4,64.1c2.8-0.7,5.2-2.1,7.2-4.1l17.6,10.1C72.4,78,63.6,83.3,53.4,84.3V64.1z M81.5,64.3L63.9,54.1 c0.4-1.3,0.7-2.7,0.7-4.1c0-1.5-0.3-2.8-0.7-4.2l17.6-10.1c2,4.4,3.2,9.2,3.2,14.3C84.7,55.1,83.5,59.9,81.5,64.3z"/></g></switch><text x="0" y="115" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Alice Design</text><text x="0" y="120" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns:x="http://ns.adobe.com/Extensibility/1.0/" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" xmlns:graph="http://ns.adobe.com/Graphs/1.0/" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 125" style="enable-background:new 0 0 100 100;" xml:space="preserve"><switch><foreignObject requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/" x="0" y="0" width="1" height="1"/><g i:extraneous="self"><path d="M97.5,55.5V44.5l-10.3-1c-0.4-2.2-1-4.4-1.7-6.4l8.4-6l-5.5-9.6L79,25.7c-1.4-1.7-3-3.3-4.7-4.7l4.3-9.4L69,6.1l-6,8.4 c-2.1-0.8-4.2-1.3-6.4-1.7l-1-10.3H44.5l-1,10.3c-2.2,0.4-4.4,1-6.4,1.7l-6-8.4l-9.6,5.5l4.3,9.4c-1.7,1.4-3.3,3-4.7,4.7l-9.4-4.3 L6.1,31l8.4,6c-0.8,2.1-1.3,4.2-1.7,6.4l-10.3,1v11.1l10.3,1c0.4,2.2,1,4.4,1.7,6.4l-8.4,6l5.5,9.6l9.4-4.3c1.4,1.7,3,3.3,4.7,4.7 l-4.3,9.4l9.6,5.5l6-8.4c2.1,0.8,4.2,1.3,6.4,1.7l1,10.3h11.1l1-10.3c2.2-0.4,4.4-1,6.4-1.7l6,8.4l9.6-5.5L74.3,79 c1.7-1.4,3.3-3,4.7-4.7l9.4,4.3l5.5-9.6l-8.4-6c0.8-2.1,1.3-4.2,1.7-6.4L97.5,55.5z M78.7,50c0,3.6-0.7,7-2,10.2l-12.1-7 c0.2-1.1,0.4-2.1,0.4-3.3c0-1.1-0.1-2.2-0.4-3.3l12.1-7C78,43,78.7,46.4,78.7,50z M21.3,50c0-3.6,0.7-7.1,2-10.2l12.1,7 c-0.2,1.1-0.4,2.2-0.4,3.3c0,1.1,0.1,2.2,0.4,3.3l-12.1,7C22,57,21.3,53.6,21.3,50z M44,50c0-3.3,2.7-6,6-6c3.3,0,6,2.7,6,6 c0,3.3-2.7,6-6,6C46.7,56,44,53.3,44,50z M60.2,38.9c-1.6-1.5-3.5-2.6-5.6-3.2V21.7c7.1,1.1,13.3,4.9,17.7,10.3L60.2,38.9z M45.4,35.7c-2.1,0.7-4,1.8-5.6,3.2l-12-6.9c4.4-5.3,10.5-9.1,17.6-10.2V35.7z M39.9,61.1c1.6,1.5,3.5,2.5,5.6,3.2v14 c-7.1-1.1-13.3-4.9-17.6-10.2L39.9,61.1z M54.5,64.3c2.1-0.7,4-1.8,5.6-3.2l12,7c-4.4,5.4-10.5,9.1-17.7,10.2V64.3z"/></g></switch><text x="0" y="115" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Alice Design</text><text x="0" y="120" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 34" style="enable-background:new 0 0 32 34;" xml:space="preserve">
<style type="text/css">
.st0{fill:#000;}
.st1{fill:#599EF4;}
</style>
<g id="layer_cog">
<path class="st0" d="M31.6,16L29,15.5h0c-0.1,0-0.3-0.1-0.4-0.1c-0.2,0-0.3-0.2-0.4-0.3c-0.3-1.3-0.8-2.5-1.5-3.6
c-0.1-0.2-0.1-0.4,0-0.5c0.5-0.8,1.1-1.6,1.6-2.5c0.2-0.2,0.2-0.6-0.1-0.8c-0.7-0.7-1.4-1.4-2-2c-0.2-0.2-0.5-0.2-0.7,0l-0.9,0.6
l-1.8,1.1c-1.2,0.8-0.6,3.2,0,3.8c0.8,0.8,1.5,1.8,2,2.8c0.5,1,0.7,2.1,0.8,3.2c0,0.2,0,0.4,0,0.5s0,0.1,0,0.1c0,0.5,0,1.1-0.1,1.7
c-0.2,1.2-0.6,2.4-1.2,3.4c-0.6,1.1-1.5,2-2.5,2.8c-1.7,1.3-3.8,2-5.9,2l0,0c-2.1,0-4.2-0.7-5.9-2c-1-0.8-1.8-1.7-2.5-2.8
c-0.6-1-1.1-2.2-1.2-3.4c-0.1-0.5-0.1-1.1-0.1-1.7c0,0,0-0.1,0-0.1s0-0.3,0.1-0.5c0.2-2.3,1.2-4.4,2.8-6c0.6-0.6,1.2-3,0-3.8
L7.3,6.2L6.4,5.6c-0.2-0.2-0.5-0.2-0.7,0c-0.7,0.7-1.3,1.4-2,2C3.5,7.8,3.5,8.2,3.7,8.4c0.5,0.8,1.1,1.6,1.6,2.5
c0.1,0.2,0.1,0.4,0,0.5c-0.7,1.1-1.2,2.3-1.5,3.6c0,0.2-0.2,0.3-0.4,0.3c-0.1,0-0.3,0.1-0.4,0.1H3L0.4,16C0.2,16,0,16.3,0,16.5v2.9
C0,19.7,0.2,20,0.4,20l1.6,0.3l1.4,0.3c0.1,0,0.3,0.1,0.3,0.3c0.3,1.3,0.8,2.5,1.5,3.6c0.1,0.2,0.1,0.4,0,0.5
C4.8,25.7,4.4,26.3,4,27l-0.4,0.6c-0.2,0.2-0.1,0.6,0.1,0.7c0.7,0.7,1.4,1.4,2.1,2.1c0.1,0.1,0.2,0.2,0.3,0.1
c0.1,0,0.3-0.1,0.4-0.2C6.7,30.2,7,30,7.2,29.9L9,28.7c0.1-0.1,0.3-0.1,0.4,0c1.1,0.7,2.4,1.2,3.6,1.5c0.2,0,0.3,0.2,0.3,0.4
c0.2,1,0.4,2,0.6,3c0,0.3,0.3,0.4,0.5,0.4h2.9c0.3,0,0.5-0.2,0.5-0.4c0.2-1,0.4-2,0.6-3c0-0.2,0.2-0.3,0.3-0.4
c1.3-0.3,2.5-0.8,3.6-1.5c0.1-0.1,0.3-0.1,0.4,0l1.8,1.2c0.2,0.2,0.5,0.3,0.7,0.5c0.1,0.1,0.3,0.1,0.4,0.2c0.1,0,0.3,0,0.4-0.1
c0.7-0.7,1.4-1.4,2.1-2.1c0.2-0.2,0.3-0.5,0.1-0.7L28,26.9c-0.4-0.6-0.8-1.3-1.2-1.9c-0.1-0.2-0.1-0.4,0-0.5
c0.7-1.1,1.2-2.4,1.5-3.6c0-0.1,0.1-0.2,0.3-0.3l1.4-0.3l1.6-0.3c0.3,0,0.4-0.3,0.4-0.5v-2.9C32,16.3,31.8,16,31.6,16z"/>
</g>
<g id="layer_flame">
<path class="st1" d="M16.5,23.4c0.6-0.9,0.9-2.1,0.6-3.2c-0.1-0.7-0.4-1.4-0.9-1.9c0.1,0.4,0.1,0.8,0.2,1.2c0,0.3,0,0.6-0.1,0.9
c-0.2,0.5-0.7,0.7-1.1,0.5c-0.1-0.1-0.2-0.1-0.3-0.3c-0.3-0.4-0.5-0.9-0.4-1.5c0-0.4,0.1-0.7,0.2-1.2c-0.1,0.1-0.3,0.1-0.4,0.2
c-0.9,0.7-1.4,1.7-1.5,2.9c-0.1,1.5,0.2,3,1,4.3l0.1,0.2c-0.1,0-0.1,0-0.2-0.1c-2.8-1.2-4.6-4.1-4.5-7.3c0-1.3,0.4-2.6,1-3.7
c0.6-0.7,1.1-1.5,1.5-2.4c0.2-0.6,0.4-1.3,0.5-2c0-0.2,0-0.4,0.1-0.6c0.3,0.6,0.5,1.3,0.6,2c0.1,0.7,0,1.5-0.2,2.2
c0.4-0.3,0.8-0.7,1.1-1.1c0.6-0.9,0.9-2,1-3c0-0.8,0-1.5,0-2.3c0-0.8,0.1-1.5,0.2-2.3c0.3-1.6,1.5-2.9,2.9-3.3h0.1c0,0,0,0,0,0.1
c-0.6,1.4-0.6,3,0,4.4c0.7,1.5,1.5,3,2.3,4.5c0.8,1.5,1.3,2.1,2,3.5c0.4,1.1,0.7,2.3,0.7,3.5c0,2-0.8,3.9-2.3,5.1
c-0.3,0.2-0.6,0.5-0.9,0.6l0.1-0.3c0.5-1.3,0.6-2.7,0.3-4c-0.2-0.7-0.4-1.3-0.8-1.9c-0.3-0.5-0.6-0.9-0.9-1.4
c-0.3-0.5-0.5-1.1-0.6-1.7c0-0.5,0-0.9,0-1.4c-0.1,0.1-0.2,0.3-0.3,0.5c-0.5,0.9-0.6,1.9-0.3,2.9c0.2,0.7,0.5,1.5,0.7,2.2
c0.2,0.7,0.4,1.4,0.4,2.1C18.4,21.7,17.7,22.8,16.5,23.4L16.5,23.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 32 34" style="enable-background:new 0 0 32 34;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#599EF4;}
</style>
<g id="layer_cog">
<path class="st0" d="M31.6,16L29,15.5h0c-0.1,0-0.3-0.1-0.4-0.1c-0.2,0-0.3-0.2-0.4-0.3c-0.3-1.3-0.8-2.5-1.5-3.6
c-0.1-0.2-0.1-0.4,0-0.5c0.5-0.8,1.1-1.6,1.6-2.5c0.2-0.2,0.2-0.6-0.1-0.8c-0.7-0.7-1.4-1.4-2-2c-0.2-0.2-0.5-0.2-0.7,0l-0.9,0.6
l-1.8,1.1c-1.2,0.8-0.6,3.2,0,3.8c0.8,0.8,1.5,1.8,2,2.8c0.5,1,0.7,2.1,0.8,3.2c0,0.2,0,0.4,0,0.5s0,0.1,0,0.1c0,0.5,0,1.1-0.1,1.7
c-0.2,1.2-0.6,2.4-1.2,3.4c-0.6,1.1-1.5,2-2.5,2.8c-1.7,1.3-3.8,2-5.9,2l0,0c-2.1,0-4.2-0.7-5.9-2c-1-0.8-1.8-1.7-2.5-2.8
c-0.6-1-1.1-2.2-1.2-3.4c-0.1-0.5-0.1-1.1-0.1-1.7c0,0,0-0.1,0-0.1s0-0.3,0.1-0.5c0.2-2.3,1.2-4.4,2.8-6c0.6-0.6,1.2-3,0-3.8
L7.3,6.2L6.4,5.6c-0.2-0.2-0.5-0.2-0.7,0c-0.7,0.7-1.3,1.4-2,2C3.5,7.8,3.5,8.2,3.7,8.4c0.5,0.8,1.1,1.6,1.6,2.5
c0.1,0.2,0.1,0.4,0,0.5c-0.7,1.1-1.2,2.3-1.5,3.6c0,0.2-0.2,0.3-0.4,0.3c-0.1,0-0.3,0.1-0.4,0.1H3L0.4,16C0.2,16,0,16.3,0,16.5v2.9
C0,19.7,0.2,20,0.4,20l1.6,0.3l1.4,0.3c0.1,0,0.3,0.1,0.3,0.3c0.3,1.3,0.8,2.5,1.5,3.6c0.1,0.2,0.1,0.4,0,0.5
C4.8,25.7,4.4,26.3,4,27l-0.4,0.6c-0.2,0.2-0.1,0.6,0.1,0.7c0.7,0.7,1.4,1.4,2.1,2.1c0.1,0.1,0.2,0.2,0.3,0.1
c0.1,0,0.3-0.1,0.4-0.2C6.7,30.2,7,30,7.2,29.9L9,28.7c0.1-0.1,0.3-0.1,0.4,0c1.1,0.7,2.4,1.2,3.6,1.5c0.2,0,0.3,0.2,0.3,0.4
c0.2,1,0.4,2,0.6,3c0,0.3,0.3,0.4,0.5,0.4h2.9c0.3,0,0.5-0.2,0.5-0.4c0.2-1,0.4-2,0.6-3c0-0.2,0.2-0.3,0.3-0.4
c1.3-0.3,2.5-0.8,3.6-1.5c0.1-0.1,0.3-0.1,0.4,0l1.8,1.2c0.2,0.2,0.5,0.3,0.7,0.5c0.1,0.1,0.3,0.1,0.4,0.2c0.1,0,0.3,0,0.4-0.1
c0.7-0.7,1.4-1.4,2.1-2.1c0.2-0.2,0.3-0.5,0.1-0.7L28,26.9c-0.4-0.6-0.8-1.3-1.2-1.9c-0.1-0.2-0.1-0.4,0-0.5
c0.7-1.1,1.2-2.4,1.5-3.6c0-0.1,0.1-0.2,0.3-0.3l1.4-0.3l1.6-0.3c0.3,0,0.4-0.3,0.4-0.5v-2.9C32,16.3,31.8,16,31.6,16z"/>
</g>
<g id="layer_flame">
<path class="st1" d="M16.5,23.4c0.6-0.9,0.9-2.1,0.6-3.2c-0.1-0.7-0.4-1.4-0.9-1.9c0.1,0.4,0.1,0.8,0.2,1.2c0,0.3,0,0.6-0.1,0.9
c-0.2,0.5-0.7,0.7-1.1,0.5c-0.1-0.1-0.2-0.1-0.3-0.3c-0.3-0.4-0.5-0.9-0.4-1.5c0-0.4,0.1-0.7,0.2-1.2c-0.1,0.1-0.3,0.1-0.4,0.2
c-0.9,0.7-1.4,1.7-1.5,2.9c-0.1,1.5,0.2,3,1,4.3l0.1,0.2c-0.1,0-0.1,0-0.2-0.1c-2.8-1.2-4.6-4.1-4.5-7.3c0-1.3,0.4-2.6,1-3.7
c0.6-0.7,1.1-1.5,1.5-2.4c0.2-0.6,0.4-1.3,0.5-2c0-0.2,0-0.4,0.1-0.6c0.3,0.6,0.5,1.3,0.6,2c0.1,0.7,0,1.5-0.2,2.2
c0.4-0.3,0.8-0.7,1.1-1.1c0.6-0.9,0.9-2,1-3c0-0.8,0-1.5,0-2.3c0-0.8,0.1-1.5,0.2-2.3c0.3-1.6,1.5-2.9,2.9-3.3h0.1c0,0,0,0,0,0.1
c-0.6,1.4-0.6,3,0,4.4c0.7,1.5,1.5,3,2.3,4.5c0.8,1.5,1.3,2.1,2,3.5c0.4,1.1,0.7,2.3,0.7,3.5c0,2-0.8,3.9-2.3,5.1
c-0.3,0.2-0.6,0.5-0.9,0.6l0.1-0.3c0.5-1.3,0.6-2.7,0.3-4c-0.2-0.7-0.4-1.3-0.8-1.9c-0.3-0.5-0.6-0.9-0.9-1.4
c-0.3-0.5-0.5-1.1-0.6-1.7c0-0.5,0-0.9,0-1.4c-0.1,0.1-0.2,0.3-0.3,0.5c-0.5,0.9-0.6,1.9-0.3,2.9c0.2,0.7,0.5,1.5,0.7,2.2
c0.2,0.7,0.4,1.4,0.4,2.1C18.4,21.7,17.7,22.8,16.5,23.4L16.5,23.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

View File

@@ -0,0 +1,59 @@
// Other global files
@import '_variables';
@import '_mixins';
@import '_fonts';
// Theme fonts
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&family=Montserrat:wght@600;700&display=swap');
// RESETS
html {
box-sizing: border-box;
font-size: $font-size-root;
font-family: $font-family;
height: 100%;
-webkit-overflow-scrolling: touch;
line-height: 1.15;
color: $primary--base;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
html *,
html *::before,
html *::after {
box-sizing: border-box;
}
* {
margin: 0;
padding: 0;
outline: none;
}
@at-root {
@-ms-viewport { width: device-width; }
}
a {
text-decoration: none;
color: inherit;
}
ul {
list-style-type: none;
}
var {
font-style: normal;
}
// BUTTONS
// MISC.
hr {
border: none;
border-top-style: solid;
border-top-width: 1px;
border-top-color: rgba($black, .25);
}

View File

@@ -0,0 +1,6 @@
@mixin blue-glow-box($radius) {
box-shadow: 0 0 .5rem * $radius #fff, 0 0 1rem * $radius $primary--blue, 0 0 2rem * $radius $primary--blue;
}
@mixin blue-glow-text($radius) {
text-shadow: 0 0 .5rem * $radius #fff, 0 0 1rem * $radius $primary--blue, 0 0 2rem * $radius $primary--blue;
}

View File

@@ -0,0 +1,28 @@
//// COLOURS
// Global colours
$white: #FFF;
$black: #000;
$dull--black: #0D0D0D;
$primary--base: #2c3e50;
$primary--blue: #599EF4;
$secondary--blue: #355F91;
$valid: #6AC47B;
$warning: #F7A758;
$alert: #F44E4E;
// Spell colours
$spell-card--innerborder: #f5ebd8;
// FONT FAMILY BASES
$font-family: 'Lato', sans-serif;
$font-light: 300;
$font-regular: 400;
$font-bold: 700;
$font-size-root: 15px !default;
$font-size-base: 1.2rem !default;
$line-height-base: 1.3 !default;

View File

@@ -0,0 +1,112 @@
<template>
<nav class="navbar navbar-dark">
<ul v-if="links.length != 0">
<li v-for="(link, index) in links" :key="index">
<router-link v-bind:to="link.url">{{ link.text }}</router-link>
</li>
</ul>
</nav>
</template>
<script>
export default {
name: 'navbar',
data() {
return {
links: [
{
id: 0,
text: 'Accueil',
url: '/',
},
{
id: 1,
text: 'Sortilèges',
url: '/spells',
},
{
id: 2,
text: 'Monde',
url: '/world',
},
]
}
}
}
</script>
<style lang="scss">
nav {
width: 100%;
position: fixed;
top: 0;
padding-left: 2rem;
ul {
display: flex;
li {
a {
position: relative;
margin-right: 1rem;
padding: 1.5rem;
display: block;
font-size: .83rem;
font-weight: $font-bold;
text-transform: uppercase;
text-shadow: none;
transition: text-shadow .5s cubic-bezier(0.645, 0.045, 0.355, 1);
&:after {
position: absolute;
display: block;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: 0;
height: 3px;
content: '';
box-shadow: none;
transition: width .5s cubic-bezier(0.645, 0.045, 0.355, 1), box-shadow .5s cubic-bezier(0.645, 0.045, 0.355, 1);
}
&:hover,
&.router-link-exact-active {
@include blue-glow-text(.5);
&:after {
width: 80%;
transition: width .5s cubic-bezier(0.645, 0.045, 0.355, 1);
@include blue-glow-box(.5);
}
}
}
}
}
&.navbar-light {
ul {
li {
a {
color: $black;
&:after {
background: $black;
}
}
}
}
}
&.navbar-dark {
ul {
li {
a {
color: $white;
&:after {
background: $white;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,208 @@
<template>
<article class="spell-card">
<div class="content">
<header>
<h3>{{ name }}</h3>
</header>
<section class="casting-header">
<div class="casting-infos">
<div class="type">
<div class="level">
<div class="label">
<span>LV.</span>
<br>
<span>MIN</span>
</div>
<var>{{ level }}</var>
</div>
<ul class="schools">
<li v-for="(school, index) in schools" :key="index">
{{ school.name }}
</li>
</ul>
</div>
<div class="cost">
<span class="label">Malus</span>
<var>{{ cost }}</var>
</div>
<div class="charge">
<span class="label">Charge</span>
<var>{{ charge }}</var>
</div>
</div>
<div class="casting-ingredients">
<p class="title">Nécessite :</p>
<p class="content">
<span v-for="(ingredient, index) in ingredients" :key="index">
{{ ingredient.name }}<span v-if="index != Object.keys(ingredients).length - 1">, </span>
</span>
</p>
</div>
</section>
<hr>
<section class="spell-description">
<p>{{ description }}</p>
</section>
<hr>
<footer v-if="variables">
<div class="label">Variables</div>
<div class="variables">
<div v-for="(variable, index) in variables" :key="index" class="xyz">
<span>{{ variable.name }}</span>
<var>{{ variable.description }}</var>
</div>
</div>
</footer>
</div>
</article>
</template>
<script>
export default {
name: 'spell-card',
props: {
name: String,
description: String,
level: Number,
charge: Number,
cost: String,
schools: Array,
ingredients: Array,
variables: Array,
},
data() {
return {
isFlipped: false,
}
},
methods: {
toggleCard: async function() {
this.isFlipped = !this.isFlipped;
}
},
}
</script>
<style lang="scss">
.spell-card {
max-width: 35rem;
padding: .5rem;
margin: .5rem auto .5rem;
background: $spell-card--innerborder;
border: solid 1px rgba($primary--base, .5);
border-radius: .5rem;
box-shadow: .0 .0 .75rem rgba($primary--base, .25);
> .content {
max-width: 35rem;
height: 100%;
padding: .75rem;
background: $white;
header {
h3 {
padding: .5rem;
text-align: center;
color: $white;
text-transform: uppercase;
background: $black;
}
}
.casting-header {
display: flex;
> * {
width: 50%;
}
.casting-infos {
margin: .75rem 0;
padding-right: .75rem;
border-right: 1px solid rgba($black, .25);
.type {
margin-bottom: .5rem;
display: flex;
> * {
width: 50%;
}
.level {
.label {
margin-right: .25rem;
display: inline-block;
}
var {
display: inline-block;
font-size: 2.3em;
}
}
}
.cost, .charge {
font-size: 0.9rem;
> * {
vertical-align: middle;
}
.label {
display: inline-block;
width: 30%;
text-transform: uppercase;
}
var {
display: inline-block;
}
}
}
.casting-ingredients {
margin: .75rem 0;
padding-left: .75rem;
.title {
margin-bottom: .25rem;
font-size: .9rem;
font-weight: bold;
text-transform: uppercase;
}
.content {
padding-top: 1rem;
font-size: .85rem;
text-align: justify;
text-align-last: center;
}
}
}
.spell-description {
padding: .75rem 0;
font-size: .85rem;
p {
&:not(:last-of-type) {
margin-bottom: .5rem;
}
}
}
footer {
padding: .75rem 0 0;
display: flex;
align-items: center;
.label {
margin-right: 1rem;
}
.variables {
.xyz {
span {
font-size: 1.1rem;
font-weight: bold;
margin-right: 1rem;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div class="spells-list">
<spell-card v-for="(spell, index) in spells" :key="index" v-bind="spell"/>
</div>
</template>
<script>
// Components
import spellcard from "./spell-card"
// API
import { RepositoryFactory } from "../../../api/repositories"
const spellsRepository = RepositoryFactory.get('spells')
export default {
name: 'spellslist',
components: {
'spell-card': spellcard,
},
data() {
return {
loading: false,
spells: []
}
},
created() {
this.computeSpells()
},
methods: {
async fetchSpells() {
const { data } = await spellsRepository.getSpells()
return data
},
async computeSpells() {
this.loading = true
const displaySpells = await this.fetchSpells();
this.loading = false
this.spells = displaySpells.slice(0, 10)
}
}
}
</script>
<style>
</style>

20
client/src/main.js Normal file
View File

@@ -0,0 +1,20 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './app.vue'
import routes from './routes'
Vue.config.productionTip = false
Vue.use(VueRouter)
const router = new VueRouter({
mode: 'history',
routes,
});
const app = new Vue({
render: h => h(App),
router
})
app.$mount('#srs')

View File

@@ -0,0 +1,18 @@
class Spell {
private id: number
private name: string
private description: string
private cost: number
private charge: number
private schools: Array<string>
private ingredients: Array<string>
private variables: Array<string>
constructor(id: number) {
this.id = id
}
}
export default Spell;

View File

@@ -0,0 +1,57 @@
<template>
<div class="home-page">
<section class="landing">
<div class="splash-title">
<h1>Auracle</h1>
<div>
<p>Recueil de savoirs arcaniques et ésotériques des contrées lointaines de Léïm</p>
<p>Découvrez ses sortilèges et maléfices...</p>
</div>
</div>
</section>
</div>
</template>
<script>
export default {
name: 'index',
}
</script>
<style lang="scss">
.home-page {
.landing {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: $dull--black;
.splash-title {
padding-bottom: 1rem;
color: $white;
text-align: center;
@include blue-glow-text(.8);
h1 {
margin-bottom: 1.5rem;
font-size: 4rem;
font-family: 'Montserrat', sans-serif;
font-weight: $font-bold;
text-transform: uppercase;
}
div {
padding-top: 1rem;
p {
margin-bottom: 1.5rem;
font-size: 1.2rem;
font-style: italic;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,23 @@
<template>
<section class="spells-page">
<spell-list/>
</section>
</template>
<script>
// Components
import spellslist from "../components/spells/spells-list"
export default {
name: 'spells',
components: {
'spell-list': spellslist,
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,15 @@
<template>
<section class="world-page">
</section>
</template>
<script>
export default {
name: 'world',
}
</script>
<style>
</style>

25
client/src/routes.js Normal file
View File

@@ -0,0 +1,25 @@
// App router
// Pages
import index from "./pages/index-page";
import spells from "./pages/spells-page";
import world from "./pages/world-page";
// Routes
const routes = [
{
path: "*", redirect: "/",
},
{
path: '/', component: index,
},
{
path: '/spells', component: spells,
},
{
path: '/world', component: world,
}
];
export default routes;

14
client/vue.config.js Normal file
View File

@@ -0,0 +1,14 @@
module.exports = {
css: {
loaderOptions: {
sass: {
prependData: `
@import "@/assets/scss/_global.scss";
@import "@/assets/scss/_variables.scss";
@import "@/assets/scss/_mixins.scss";
@import "@/assets/scss/_fonts.scss";
`
}
}
}
};

22
database/connection.js Normal file
View File

@@ -0,0 +1,22 @@
// MODULES
const fs = require('fs')
const mysql = require('mysql')
// Fetches and parses credentials file
let credentials_data = fs.readFileSync('./database/credentials.json')
let credentials = JSON.parse(credentials_data)
const db = mysql.createConnection({
host: credentials.host,
user: credentials.user,
password: credentials.password,
database: credentials.database,
})
db.on('error', err => {
if (err.errno == 'ECONNRESET') {
console.log("The connection was reset during your request. Please try again later.");
}
})
module.exports = { db }

7
database/querytests.sql Normal file
View File

@@ -0,0 +1,7 @@
USE spellsaurus;
/* Fetches a specific spell's school(s) */
SELECT school.name
FROM spells_schools AS sc
INNER JOIN school AS school ON sc.id_school = school.id
WHERE sc.id_spell = 1;

View File

@@ -0,0 +1,82 @@
DROP DATABASE IF EXISTS spellsaurus;
CREATE DATABASE spellsaurus CHARACTER SET utf8 COLLATE utf8_bin;
USE spellsaurus;
/* ==== PRIMARY TABLES ==== */
/* SPELLS */
CREATE TABLE IF NOT EXISTS `spell` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT 'Nom du sort',
`description` VARCHAR(1000) NOT NULL DEFAULT "Description du sort",
`level` INT UNSIGNED DEFAULT 0,
`charge` INT UNSIGNED DEFAULT 0,
`cost` VARCHAR(255) DEFAULT "0",
`is_ritual` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`)
);
/* META SCHOOLS */
CREATE TABLE IF NOT EXISTS `meta_school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école mère",
`description` VARCHAR(255) DEFAULT "Description de l'école mère",
PRIMARY KEY (`id`)
);
/* SCHOOLS */
CREATE TABLE IF NOT EXISTS `school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école",
`description` VARCHAR(255) DEFAULT "Description de l'école",
`id_meta_school` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY(`id_meta_school`) REFERENCES meta_school(`id`)
);
/* COMMON INGREDIENTS */
CREATE TABLE IF NOT EXISTS `ingredient` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre",
PRIMARY KEY (`id`)
);
/* COMMON VARIABLES */
CREATE TABLE IF NOT EXISTS `variable` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`description` VARCHAR(255) NOT NULL DEFAULT "Nombre de créatures affectées",
PRIMARY KEY (`id`)
);
/* ==== ASSOCIATION TABLES ==== */
/* SPELLS' SCHOOLS */
/* One spell can have multiple (up to 3) schools */
CREATE TABLE IF NOT EXISTS `spells_schools` (
`id_spell` INT UNSIGNED NOT NULL,
`id_school` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_school`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`),
FOREIGN KEY(`id_school`) REFERENCES school(`id`)
);
/* SPELLS' VARIABLES */
/* One spell can have multiple (up to 2) variables of cost */
CREATE TABLE IF NOT EXISTS `spells_variables` (
`id_spell` INT UNSIGNED NOT NULL,
`id_variable` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_variable`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`),
FOREIGN KEY(`id_variable`) REFERENCES variable(`id`)
);
/* SPELLS' VARIABLES */
/* One spell can have multiple ingredients */
CREATE TABLE IF NOT EXISTS `spells_ingredients` (
`id_spell` INT UNSIGNED NOT NULL,
`id_ingredient` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_ingredient`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`),
FOREIGN KEY(`id_ingredient`) REFERENCES ingredient(`id`)
);

View File

@@ -0,0 +1,236 @@
SET NAMES utf8;
USE spellsaurus;
-- META SCHOOLS
INSERT INTO `meta_school` (name, description) VALUES
('Magies blanches', 'Magies disciplinant les arts de soins et de lumières.'),
('Magies noires', 'Magies disciplinant l\'art de la mort et des secrets.'),
('Magies élémentaires', 'Magies disciplinant les éléments basiques tels que l\'eau, la foudre et le feu, pour n\'en citer que les plus populaires.'),
('Magies spirituelles', 'Magies disciplinant l\'esprit, tant pour le défendre que l\'attaquer.'),
('Magies spatio-temporelles', 'Magies régissant le temps et l\'espace.'),
('Magies affiliées', 'Magies rattachées à une forme d\'énergie magique particulière.'),
('Magies autres', 'Magies trop spécifiques et ne rentrant dans aucune autre grande école.');
-- SCHOOLS
INSERT INTO `school` (name, description, id_meta_school) VALUES
('Lumomancie', 'Discipline arcanique de la lumière.', 1),
('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 1),
('Obstrumancie', 'Discipline arcanique de la protection et des sceaux.', 1),
('Tenebromancie', 'Discipline arcanique de la lumière.', 2),
('Necromancie', 'Discipline arcanique de la mort.', 2),
('Morbomancie', 'Discipline arcanique des maladies et malédictions.', 2),
('Pyromancie', 'Discipline arcanique du feu.', 3),
('Hydromancie', 'Discipline arcanique de l\'eau.', 3),
('Electromancie', 'Discipline arcanique de la foudre.', 3),
('Terramancie', 'Discipline arcanique de la terre.', 3),
('Caelomancie', 'Discipline arcanique de l\'air.', 3),
('Légimancie', 'Discipline arcanique de la lecture et du contrôle spirituel.', 4),
('Illusiomancie', 'Discipline arcanique des illusions.', 4),
('Cruciomancie', 'Discipline arcanique de la destruction spirituelle.', 4),
('Chronomancie', 'Discipline arcanique du temps.', 5),
('Spatiomancie', 'Discipline arcanique de l\'espace.', 5),
('Kénomancie', 'Discipline arcanique du néant.', 6),
('Lutomancie', 'Discipline arcanique des abysses.', 6),
('Échomancie', 'Discipline arcanique de la résolution animique.', 6),
('Protomancie', 'Discipline arcanique de la magie pure.', 7),
('Rebumancie', 'Discipline arcanique de la lumière.', 7),
('Vocamancie', 'Discipline arcanique de la lumière.', 7),
('Somamancie', 'Discipline arcanique de la maîtrise corporelle.', 7),
('Antimancie', 'Discipline arcanique de l\'annulation arcanique.', 7);
-- SPELLS
/*
SCHEMA
(name, description, level, charge, cost, is_ritual),
*/
-- LIGHT SPELLS
INSERT INTO `spell` (name, description, level, charge, cost, is_ritual) VALUES
('Rayon prismatique I', 'Un petit rayon étincelant est émis de la paume du lanceur. Ce dernier peut rebondir sur les surfaces réfléchissantes. Sil percute une cture, elle subit 6+2D6 dégâts radiants.', 0, 0, '0', false),
('Rayon prismatique II', 'Un rayon étincelant est émis de la paume du lanceur. Ce dernier peut rebondir sur les surfaces réfléchissantes. Sil percute une créature, e subit 15+2D6 dégâts radiants.', 0, 0, '-20', false),
('Rayon prismatique III', 'Un grand rayon étincelant est émis de la paume du lanceur. Ce dernier peut rebondir sur les surfaces réfléchissantes. Sil percute une cture, elle subit 25+2D6 dégâts radiants.', 0, 0, '-40', false),
('Laser prismatique', 'Les mains du lanceur silluminent dune intense aura blanche. Après un tour de canalisation, il projette un énorme laser blanc de sa paume dans une direction unique, infligeant 30+1D20+2D6 dégâts radiants aux créatures sur sa trajectoire. Le laser peut rebondir sur les surfaces réfléchissantes.', 20, 1, '-60', false),
('Globe lumineux', 'Le lanceur manifeste un petit globe de lumière autour de lui. La lumière dégagée par ce globe éclaire sur une portée initiale de 5m, mais peut être augmentée suivant la puissance du sort. Le lanceur garde un contrôle sur cette sphère, quil peut déplacer en conséquence.', 0, 0, '-10x', false),
('Globule de vérité', 'Le lanceur concentre son arcane en un globule de lumière bleue qui flottille autour de lui. Ce dernier, en plus dêtre une source de lumière considérable, permet de disperser la magie dillusion. Le lanceur manifeste un globe de lumière magique éclairant sur 5m autour de lui. Permet de voir les objets et créatures cachés par magie et de percer les illusions.', 10, 0, '-25x', false),
('Vision nocturne', 'Le lanceur octroie à lui-même ou bien à une créature quil touche une vision nettement améliorée dans lobscurité pendant 1D3 tours.', 0, 0, '-20', false),
('Œil de Lystos', 'Le lanceur octroie à lui-même ou bien à une créature quil touche le statut «Clairvoyant» pendant 1D3 tours. Elle gagne une vision nettement améliorée dans lobscurité ainsi quun avantage pour percevoir à travers la pénombre magique et les illusions.', 10, 0, '-40', false),
('Illumination', 'Par le toucher, le lanceur octroie à un petit objet une luminosité de faible intensité, semblable à la lumière vacillante dune bougie.', 0, 0, '+20', false),
('Lumière aveuglante', 'Le lanceur, en claquant des doigts, produit un bref flash de lumière devant une cible, laveuglant pendant 1D2 tours.', 0, 0, '0', false),
('Irradiation', 'Le lanceur sillumine instantanément pendant une fraction de seconde, aveuglant toutes les créatures le regardant dans un rayon de 10m pendant 1D2 tours.', 10, 0, '-25', false),
('Lystos I', 'Le lanceur, en se concentrant sur limage de Lystos dans son esprit, manifeste son sceau sous une cible unique, qui lui inflige 2D5+5 dégâts radiants ignorant l\'armure en dégageant une aura brûlante. Des énergies divines mystérieuses facilitent la canalisation du sort et sa rapidité. Si la cible tente desquiver, son action subit un malus de -15.', 0, 0, '0', false),
('Lystos II', 'Le lanceur, en se concentrant sur limage de Lystos dans son esprit, manifeste son sceau sous une cible unique, qui lui inflige 2D8+8 dégâts radiants ignorant l\'armure en dégageant une aura brûlante. Des énergies divines mystérieuses facilitent la canalisation du sort et sa rapidité. Si la cible tente desquiver, son action subit un malus de -30.', 0, 0, '-20', false),
('Lystos III', 'Le lanceur, en se concentrant sur limage de Lystos dans son esprit, manifeste son sceau sous une cible unique, qui lui inflige 2D10+10 dégâts radiants ignorant l\'armure en dégageant une aura brûlante. Des énergies divines mystérieuses facilitent la canalisation du sort et sa rapidité. Si la cible tente desquiver, son action subit un malus de -45.', 0, 0, '-40', false),
('Paradigme Lystonien', 'Le lanceur simprègne de limage de Lystos, et la concentre dans son esprit. Son sceau, entouré de plusieurs petits sceaux, se manifestent sous un petit groupe de cibles. Ces dernières sont brûlées par la lumière se dégageant des emblèmes, et subissent 2D20+20 dégâts radiants ignorant l\'armure. Une énergie divine mystérieuse pénètre lesprit des cibles, ralentissant leurs mouvements et drainant leur volonté. Toute tentative desquive subit un malus de -60.', 15, 1, '-60', false),
('Corps luminescent', 'Le lanceur transmute une partie plus ou moins grande de son corps en énergie luminescente, lui permettant de sen servir comme extension ou comme arme. La partie transmutée permet de passer à travers les objets et surfaces peu épaisses.', 0, 0, '?', false),
('Jugement lumineux', 'Le corps du lanceur brille dune aura blanche tandis quil manifeste une guillotine de lumière au niveau du cou dune cible unique. La lame de lumière passe au travers de sa gorge et lui réduit ses PV de moitié.', 20, 0, '-80', false),
('Étoile de l\'aube', 'Une sphère de lumière initialement pâle, puis très lumineuse, se manifeste à 20m du sol et du lanceur. Au deuxième tour de canalisation, elle sabat au sol dans une explosion iridescente, soufflant toutes les créatures dans un rayon de 20m, leur infligeant 60+3D12+3D6 dégâts radiants.', 25, 2, '-120', false),
('Lumière pourrie I', 'Le lanceur conjure une orbe lumineuse verdâtre jusquà 5m autour de lui. Cette dernière, au rythme de ses palpitations, projette des rayons brûlants dans toutes les directions autour delle dans un rayon de 20m. Toutes les créatures à découvert dans un rayon de 20m de la sphère (sauf le lanceur) doivent réussir un jet de DEX. Si elles échouent, elles subissent 2D10 dégâts radiants et 1D10 dégâts nécrotiques. Elles perdent également 1D6 PV max.', 20, 0, '-30', false),
('Lumière pourrie II', 'Le lanceur conjure une orbe lumineuse verdâtre jusquà 5m autour de lui. Cette dernière, au rythme de ses palpitations, projette des rayons brûlants dans toutes les directions autour delle dans un rayon de 20m. Toutes les créatures à découvert dans un rayon de 30m de la sphère (sauf le lanceur) doivent réussir un jet de DEX. Si elles échouent, elles subissent 2D20 dégâts radiants et 1D20 dégâts nécrotiques. Elles perdent également 1D8 PV max.', 20, 0, '-50', false),
('Lumière pourrie III', 'Le lanceur conjure une orbe lumineuse verdâtre jusquà 5m autour de lui. Cette dernière, au rythme de ses palpitations, projette des rayons brûlants dans toutes les directions autour delle dans un rayon de 20m. Toutes les créatures à découvert dans un rayon de 50m de la sphère (sauf le lanceur) doivent réussir un jet de DEX. Si elles échouent, elles subissent 2D20 dégâts radiants et 2D20 dégâts nécrotiques. Elles perdent également 1D10 PV max.', 25, 0, '-70', false),
('Soleil pourri', 'Le lanceur projette un petit astre vert jusquà 20m autour de lui. Lastre brûle lair environnant, nécrosant rapidement les formes de vies dans un rayon de 100m. Toutes les créatures dans la portée de lastre (sauf le lanceur) doivent réussir un jet de CON avec un malus de -30. Si elles échouent, elles subissent 3D20 dégâts radiants et 2D20 dégâts nécrotiques. Elles perdent également 1D10 PV max.', 30, 2, '-90', false),
('Brisaube', 'Le lanceur insufle une arme qu\'il touche d\'une quantité phénoménale de magie lumineuse, de telle sorte à ce qu\'elle brille de milles feux. L\'arme inflige 2D20+1D12 dégâts radiants supplémentaires pendant 3 tours et inflige le statut "Brûlures solaires" pendant 1D4 tours si elle touche une créature.', 50, 0 , '-80', false),
('Consécration', 'Le lanceur conjure X sphères runiques dans un rayon de 40m autour de lui. Après un court instant, ces dernières explosent d\'une vive lumière blanche, infligeant 2D8+2D6 dégâts radiants aux créatures se trouvant à au moins 2m autour d\'elles.', 80, 0, '-15x', false),
('Apothéose', 'Un cercle runique apparait derrière le lanceur. Ce dernier manifeste des centaines de petits cristaux de lumière en son bord qui fondent sur les cibles dans le champ de vision du lanceur, infligeant 4D2*4D2 dégâts à chacune d\'entre elles.', 80, 1, '-100', false);
INSERT INTO `spell` (name, description, level, charge, cost, is_ritual) VALUES
('Guérison I', 'Rend 2D4+4 PV, soigne les contusions légères et blessures superficielles.', 0, 0, '0', false),
('Guérison II', 'Rend 2D6+6 PV, soigne les blessures mineures.', 0, 0, '-10', false),
('Guérison III', 'Rend 2D10+10 PV, soigne les blessures profondes.', 0, 0, '-20', false),
('Guérison IV', 'Rend 2D20+20 PV, soigne les blessures graves et stoppe les hémorragies.', 0, 0, '-40', false),
('Thaumaturgie', 'Débordant daltruisme, le lanceur canalise larcane autour de lui afin de panser les blessures dune créature. La créature ciblée regagne 3D20+30 PV. Ses blessures graves se guérissent instantannément et toute hémorragie cesse. Si la cible se trouvait dans un état de mort imminente, elle est considérée comme stabilisée.', 20, 1, '-60', false),
('Régénération I', 'Rend 1D4+2 PV pendant 3 tours à une créature, soigne les contusions légères et blessures superficielles.', 0, 0, '0', false),
('Régénération II', 'Rend 1D6+3 PV pendant 3 tours à une créatures, soigne les blessures mineures.', 0, 0, '-5', false),
('Régénération III', 'Rend 1D10+5 PV pendant 3 tours à une créature, soigne les blessures profondes.', 0, 0, '-15', false),
('Régénération IV', 'Rend 1D20+10 PV pendant 3 tours à une créature, soigne les blessures graves et stoppe les hémorragies.', 0, 0, '-30', false),
('Argaturgie', 'Le lanceur duplique sa force vitale afin de soigner sur la durée les blessures d\'une créature unique. Elle regagne 3D10+15 PV pendant 3 tours et soigne les blessures et ses hémorragies importantes.', 20, 1, '-50', false),
('Guérison collective I', 'Rend 1D4+4 PV à plusieurs créatures, soigne les contusions légères et blessures superficielles.', 0, 0, '-2x', false),
('Guérison collective II', 'Rend 1D6+6 PV à plusieurs créatures, soigne les blessures mineures.', 0, 0, '-5x', false),
('Guérison collective III', 'Rend 1D10+10 PV à plusieurs créatures, soigne les blessures profondes.', 0, 0, '-10x', false),
('Guérison collective IV', 'Rend 1D20+20 PV à plusieurs créatures, soigne les blessures graves et stoppe les hémorragies.', 0, 0, '-15x', false),
('Polythurgie', 'Le lanceur transmet de l\'énergie vitale pure à plusieurs créatures. Elles regagnent 3D10+30 PV et leurs hémorragies graves s\'estompent. Si les cibles se trouvaient dans un état de mort imminente, elles sont considérées comme stabilisées.', 20, 1, '-20x', false),
('Détoxication I', 'Renforce les défenses immunitaires d\'une créature et permet à son corps de se défendre contre un faible poison. Donne +10 CON au prochain jet contre ce dernier.', 0, 0, '0', false),
('Détoxication II', 'Renforce les défenses immunitaires d\'une créature et permet à son corps de se défendre contre un poison. Donne +20 CON aux 2 prochains jets contre ce dernier.', 0, 0, '-10', false),
('Détoxication III', 'Renforce grandement les défenses immunitaires d\'une créature et permet à son corps de se défendre contre un poison sévère. Donne +30 CON aux 2 prochains jets contre ce dernier.', 0, 0, '-30', false),
('Thératurgie', 'Renforce énormément les défenses immunitaires d\'une créature et permet au corps d\'expulser un poison mortel et puissant.', 20, 1, '-50', false),
('Détection de vie', 'Le lanceur peut déterminer la force vitale restante d\'une créature dans son champ de vision jusqu\'à 15m de distance.', 0, 0, '0', false),
('Œil de Sotana', 'Octroie au lanceur une vision thermique des 5 mètres alentours permettant de voir la force vitale des créatures conscientes environnantes. Plus une forme apparait clairement au lanceur, plus elle est en bonne santé.', 10, 0, '-15x', false),
('Filin de survie', 'Relie le corps du lanceur à celui d\'une autre créature par un filin de lumière blanche intangible. Les dégâts subis par les deux sont divisés et répartis équitablement pendant 3 tours.', 15, 0, '-30', false),
('Contre-mort', 'Le lanceur infuse une force vitale conséquente à une cible. Si elle reçoit une attaque fatale, elle conserve un unique point de vie à la place. L\'effet du sort dure une heure.', 15, 0, '-50', false),
('Sotana', 'En brûlant la moitié de ses PV max, le lanceur concentre sa volonté et son énergie vitale de manière à ramener à 1 PV une créature décédée depuis 2h maximum. Pour que ce sort se conclue, il faut impérativement que l\'âme de la créature ne soit ni détruite ni scellée, et que le lanceur soit en contact avec une partie du corps de la cible, ou une de ses possessions (qui sera consommée par le sort dans les deux cas)', 25, 0, '-80', false),
('Masotana', 'Le lanceur manifeste une zone d\'énergie vitale pure autour de lui sur environ 10m, empêchant à toutes les créatures dans cette zone de mourir pendant 5 tours. Les créatures peuvent toujours être incapacitées et ressentent la douleur, mais ne peuvent pas descendre en dessous de 1 PV.', 30, 1, '-100', false);
INSERT INTO `spell` (name, description, level, charge, cost, is_ritual) VALUES
('Bouclier arcanique I', 'Dresse une épaisse barrière d\'énergie magique autour d\'une ou plusieurs personnes, bloquant 15 points de dégâts.', 0, 0, '-10x', false),
('Bouclier arcanique II', 'Dresse une épaisse barrière d\'énergie magique autour d\'une ou plusieurs personnes, bloquant 30 points de dégâts.', 0, 0, '-20x', false),
('Bouclier arcanique III', 'Dresse une épaisse barrière d\'énergie magique autour d\'une ou plusieurs personnes, bloquant 60 points de dégâts.', 0, 0, '-40x', false),
('Isolation arcanique', 'Dresse une épaisse barrière d\'énergie magique pure autour d\'une ou plusieurs personnes, bloquant 100 points de dégâts.', 20, 0, '-60x', false),
('Bouclier étendu I', 'Conjure une unique barrière magique autour de plusieurs personnes, bloquant 50 points de dégâts avant de se briser.', 10, 0, '-30-10x', false),
('Bouclier étendu II', 'Conjure une unique barrière autour magique de plusieurs personnes, bloquant 90 points de dégâts avant de se briser.', 10, 0, '-50-10x', false),
('Barricades arcaniques', 'Conjure une unique épaisse barrière magique autour de plusieurs personnes, bloquant 120 points de dégâts avant de se briser.', 20, 0, '-70-10x', false),
('Barrière régénératrice I', 'Le lanceur dresse une barrière protectrice autour d\'une ou plusieurs créature(s), les protégeant de 20 points de dégâts. Les créatures affectées par ce sort regagnent 1D6 PV par tour tant que la barrière persiste.', 15, 0, '-20x', false),
('Barrière régénératrice II', 'Le lanceur dresse une barrière protectrice autour d\'une ou plusieurs créature(s), les protégeant de 40 points de dégâts. Les créatures affectées par ce sort regagnent 2D6 PV par tour tant que la barrière persiste.', 15, 0, '-40x', false),
('Cocon guérisseur', 'Le lanceur dresse une épaisse barrière protectrice brilliante autour d\'une ou plusieurs créature(s), les protégeant de 60 points de dégâts. Les créatures affectées par ce sort regagnent 3D8 PV par tour tant que la barrière persiste.', 20, 0, '-60x', false),
('Verrou arcanique I', 'Protège de manière magique un objet ouvrable (coffre, livre, etc...). L\'objet est scellé et ne peut plus s\'ouvrir, sauf sur volonté du lanceur. Résiste à 15 points de dégâts magiques. Une personne peut l\'ouvrir si elle inflige assez de dégâts au verrou, ou si elle réussi un jet d\'arcane avec un malus de 20.', 5, 0, '-15', false),
('Verrou arcanique II', 'Protège de manière magique un objet ouvrable (coffre, livre, etc...). L\'objet est scellé et ne peut plus s\'ouvrir, sauf sur volonté du lanceur. Résiste à 30 points de dégâts magiques. Une personne peut l\'ouvrir si elle inflige assez de dégâts au verrou, ou si elle réussi un jet d\'arcane avec un malus de 40.', 10, 0, '-30', false),
('Verrou arcanique III', 'Protège de manière magique un objet ouvrable (coffre, livre, etc...). L\'objet est scellé et ne peut plus s\'ouvrir, sauf sur volonté du lanceur. Résiste à 50 points de dégâts magiques. Une personne peut l\'ouvrir si elle inflige assez de dégâts au verrou, ou si elle réussi un jet d\'arcane avec un malus de 60.', 15, 0, '-50', false),
('Chaînes du souverain', 'Manifeste des chaînes blanches sur un objet (coffre, livre, etc...). L\'objet est scellé et ne peut plus s\'ouvrir, sauf sur volonté du lanceur. Les chaînes se dissipent et ne réapparaissent qu\'avec une tentative d\'effraction. Résiste à 75 points de dégâts magiques. Une personne peut l\'ouvrir si elle inflige assez de dégâts au verrou, ou si elle réussi un jet d\'arcane avec un malus de 90.', 20, 1, '-75', false),
('Sceau des grands anciens', 'Au court d\'un rituel de canalisation intense et épuisant, le lanceur utilise toute l\'énergie magique de son corps et une partie de sa force vitale afin d\'isoler complètement une chose quelconque de l\'existence, la plaçant effectivement entre les dimensions et du temps. La force et la durabilité du sceau dépend de la force magique du lanceur. Pour un Dieu par exemple, le sceau dure environ 5000 ans. Le sceau n\'est pas indestructible, et est susceptible à l\'usure du temps, des attaques répétées, etc...', 99, 99, '?', true),
('Ascension', 'Réveille la partie de l\'âme scellée d\'une créature, l\'unifiant dans un seul corps, et lui permettant de maitriser l\'échomancie.', 99, 5, '?', false);
-- DARKNESS SPELLS
INSERT INTO `spell` (name, description, level, charge, cost, is_ritual) VALUES
('Onde noire I', 'Projette une petite onde d\'énergie noire sur 5m autour du lanceur, infligeant 6+2D6 points de dégâts obscurs.', 0, 0, '0', false),
('Onde noire II', 'Projette une onde d\'énergie noire sur 10m autour du lanceur, infligeant 15+2D6 points de dégâts obscurs.', 0, 0, '-20', false),
('Onde noire III', 'Projette une vaste onde d\'énergie noire sur 20m autour du lanceur, infligeant 25+2D6 points de dégâts obscurs.', 0, 0, '-40', false),
('Vibrobscur', 'Projette une vague d\'énergie ténébreuse sur 40m autour du lanceur, infligeant 30+1D20+2D6 points de dégâts obscurs.', 20, 1, '-60', false),
('Ményl I', 'Le lanceur, en se concentrant sur la forme de Ményl dans son esprit, manifeste son sceau sous une cible unique, qui lui inflige 2D5+5 dégâts obscurs ignorant l\'armure en dégageant une aura glaciale. Des énergies divines mystérieuses facilitent la canalisation du sort et sa rapidité. Si la cible tente desquiver, son action subit un malus de -15.', 0, 0, '0', false),
('Ményl II', 'Le lanceur, en se concentrant sur la forme de Ményl dans son esprit, manifeste son sceau sous une cible unique, qui lui inflige 2D8+8 dégâts obscurs ignorant l\'armure en dégageant une aura glaciale. Des énergies divines mystérieuses facilitent la canalisation du sort et sa rapidité. Si la cible tente desquiver, son action subit un malus de -30.', 0, 0, '-20', false),
('Ményl III', 'Le lanceur, en se concentrant sur la forme de Ményl dans son esprit, manifeste son sceau sous une cible unique, qui lui inflige 2D10+10 dégâts obscurs ignorant l\'armure en dégageant une aura glaciale. Des énergies divines mystérieuses facilitent la canalisation du sort et sa rapidité. Si la cible tente desquiver, son action subit un malus de -45.', 0, 0, '-40', false),
('Vision Ménélienne', 'Le lanceur simprègne de l\'énergie de Ményl, et la concentre dans son esprit. Son sceau, entouré de plusieurs petits sceaux, se manifestent sous un petit groupe de cibles. Ces dernières sont brûlées de froid par les ténèbres se dégageant des emblèmes, et subissent 2D20+20 dégâts obscurs ignorant l\'armure. Une énergie divine mystérieuse pénètre lesprit des cibles, ralentissant leurs mouvements et drainant leur volonté. Toute tentative desquive subit un malus de -60.', 15, 1, '-60', false),
('Corps sombre', 'Le lanceur transmute une partie plus ou moins grande de son corps en énergie ténébreuse, lui permettant de sen servir comme extension ou comme arme. La partie transmutée permet de passer à travers les objets et surfaces peu épaisses.', 0, 0, '?', false),
('Sangsue I', 'Une dizaine de petites sphères violacées fusent vers la cible, infligeant 2D8 dégats obscurs. Le lanceur récupère les dégâts subis.', 5, 0, '-10', false),
('Sangsue II', 'Une dizaine de sphères violacées fusent vers la cible, infligeant 4D8 dégats perçants. Le lanceur récupère les dégâts subis.', 5, 0, '-25', false),
('Sangsue III', 'Une dizaine de grandes sphères violacées fusent vers la cible, infligeant 6D8 dégats obscurs. Le lanceur récupère les dégâts subis.', 5, 0, '-50', false),
('Exsangue', 'Une vingtaine de sphères et d\'éclairs noirs frappent la cible, infligeant 8D8 dégâts obscurs. Le lanceur récupère les dégâts subis. La cible doit également faire un jet de CON. En cas d\'échec, elle subit le statut "Fatigue II".', 20, 1, '-75', false),
('Zona I', 'Le lanceur projette une ombre circulaire au sol qui s\'étend sur 2m de diamètre. Des tentacules noirs se manifestent à sa surface et infligent (1D4)D3 aux entités à maximum 2m de l\'ombre chaque tour. L\'ombre persiste pendant 1D3 tours.', 5, 0, '-20', false),
('Zona II', 'Le lanceur projette une large ombre circulaire au sol qui s\'étend sur 2m de diamètre. Des tentacules noirs se manifestent à sa surface et infligent (1D6)D3 aux entités à maximum 2m de l\'ombre chaque tour. L\'ombre persiste pendant 1D3 tours.', 5, 0, '-40', false),
('Zoness', 'Le lanceur projette un duo d\'ombres circulaires au sol qui s\'étendent sur 4m de diamètre. Des tentacules noirs se manifestent à sa surface et infligent (1D10)D3 aux entités à maximum 3m de l\'ombre chaque tour. L\'ombre persiste pendant 1D4 tours.', 15, 0, '-60', false),
('Pénombres I', 'Englobe une zone de 5m autour du lanceur dans les ténèbres, diminuant la visibilité.', 0, 0, '-10-20x', false),
('Pénombres II', 'Englobe une zone de 5m autour du lanceur dans les ténèbres, diminuant grandement la visibilité.', 0, 0, '-20-20x', false),
('Pénombres III', 'Englobe une zone de 7m autour du lanceur dans les ténèbres, diminuant énormément la visibilité.', 0, 0, '-30-20x', false),
('Enclave abyssale', 'Englobe une zone de 7m autour du lanceur dans les ténèbres, rendant la visibilité quasi-nulle. Des tentacules d\'énergie noire transpercent les ennemis présents dans la zone, infligeant 2D8 dégâts obscurs par tour.', 15, 0, '-40-20x', false),
('Aiguillons de l\'ombre', 'Le lanceur manifeste X flèchettes noires qu\'il projette sur une cible, infligeant 1DX+X de dégâts obscurs ignorant l\'armure. La cible doit effectuer un jet de CON avec un malus de X. Si elle échoue, elle gagne le statut "Brisé" pendant 2 tours.', 0, 0, '-2x', false),
('Morsures de la nuit', 'Le lanceur façonne l\'obscurité autour d\'une cible en forme de machoîres noires, qui se referment brutalement sur cette dernière, lui infligeant 2D12+2D8 dégâts obscurs ignorant l\'armure.', 0, 0, '-15', false),
('Un avec les ombres', 'Le lanceur, tapi dans une ombre distincte, se fond en elle. Il surgit ensuite d\'une ombre adjacente de cette dernière dans une portée de 5m. Le lanceur est invulnérable pendant qu\'il se téléporte d\'une ombre à une autre.', 10, 0, '-20', false),
('Décret de l\'ombre', 'Le corps du lanceur brille d\'une aura sombre et froide tandis qu\'il manifeste une faux noire derrière une cible unique. La lame le fauche et lui réduit ses PV de moitié.', 20, 0, '-80', false),
('Étoile du crépuscule', 'Une sphère noire d\'encre se manifeste dans les airs au dessus du lanceur. Au deuxième tour après canalisation, elle se fracasse au sol, soufflant les créatures dans un rayon de 20m, leur infligeant 60+3D12+3D6 dégâts obscurs.', 25, 0, '-100', false);
INSERT INTO `spell` (name, description, level, charge, cost, is_ritual) VALUES
('Projection spectrale I', 'Le lanceur canalise sa magie pour manifester les émotions négatives d\'un défunt à partir du Voile et les projeter sur une cible, infligeant 8D3+8 de dégâts.', 5, 0, '-20', false),
('Projection spectrale II', 'Le lanceur canalise sa magie pour manifester les émotions négatives de deux défunts à partir du Voile et les projeter sur une cible, infligeant 8D3+3D8+8 de dégâts.', 5, 0, '-40', false),
('Projection spectrale III', 'Le lanceur manifeste un esprit haineux à partir du Voile se jetant sur la cible, infligeant 8D8 de dégâts.', 5, 0, '-60', false),
('Force spectrale', 'Le lanceur ouvre un portail vers le Voile qui déverse un torrent d\'émotions négatives et de regrets sur une cible, lui infligeant 10D10 de dégâts. La cible doit également effectuer 3 jets de VOL. Pour chaque échec, elle est tétanisée et incapable d\'agir pendant un tour.', 20, 0, '-80', false),
('Œil de Sergalée', 'Le lanceur utilise la mémoire magique afin de revivre les dernières secondes d\'une créature consciente décédée. Il doit cependant connaître le nom et le visage de la créature. L\'âme de la créature doit également être intacte pour que le sort se conclue.', 0, 0, '-15x', false),
('Kerato I', 'Le lanceur ramine un cadavre à l\'aide de sa force magique pendant 2D2 tours, obéissant à sa volonté. Les stats du cadavre sont égales à celles qu\'il possédait de son vivant, avec un malus global de -20. Le cadavre est faible à la magie blanche et immunisé contre la magie noire. Le cadavre ne peut pas lancer de sorts.', 15, 0, '-x', false),
('Kerato II', 'Le lanceur ramine un cadavre à l\'aide de sa force magique pendant 2D3 tours, obéissant à sa volonté. Les stats du cadavre sont égales à celles qu\'il possédait de son vivant, avec un malus global de -10. Le cadavre est faible à la magie blanche et immunisé contre la magie noire. Le cadavre ne peut pas lancer de sorts.', 15, 0, '-x', false),
('Kerato III', 'Le lanceur ramine un cadavre à l\'aide de sa force magique pendant 2D4 tours, obéissant à sa volonté. Les stats du cadavre sont égales à celles qu\'il possédait de son vivant. Le cadavre est faible à la magie blanche et immunisé contre la magie noire. Le cadavre ne peut pas lancer de sorts.', 20, 0, '-x', false),
('Sang pourri', 'Le lanceur ramine un cadavre à l\'aide de sa force magique pendant 2D5 tours, obéissant à sa volonté. Les stats du cadavre sont égales à celles qu\'il possédait de son vivant, avec un bonus global de 10. Le cadavre est faible à la magie blanche et immunisé contre la magie noire. Le cadavre ne peut pas lancer de sorts.', 25, 0, '-x', false),
('Mémoire de chair', 'Un cadavre animé sous le contrôle du lanceur regagne un aspect de son esprit, comme sa personnalité ou ses souvenirs, et s\'émancipe de la volonté du lanceur. Confère la capacité au cadavre de lancer des sorts. L\'âme de la créature doit également être intacte pour que le sort se conclue.', 15, 0, '?', false),
('Putréfaction I', 'Le lanceur, au toucher d\'une créature, utilise sa magie pour accélérer temporairement le vieillissement de ses cellules, lui infligeant 1D6 dégâts nécrotiques et lui faisant perdre 1D8 PV max. La créature souffre également du statut "Fatigue I".', 10, 0, '-15', false),
('Putréfaction II', 'Le lanceur, au toucher d\'une créature, utilise sa magie pour accélérer temporairement le vieillissement de ses cellules, lui infligeant 1D8 dégâts nécrotiques et lui faisant perdre 1D10 PV max. La créature souffre également du statut "Fatigue II".', 15, 0, '-30', false),
('Putréfaction III', 'Le lanceur, au toucher d\'une créature, utilise sa magie pour accélérer temporairement le vieillissement de ses cellules, lui infligeant 1D10 dégâts nécrotiques et lui faisant perdre 1D12 PV max. La créature souffre également du statut "Fatigue III".', 15, 0, '-45', false),
('À travers les âges', 'Le lanceur, au toucher d\'une créature, utilise sa magie pour accélérer exponentiellement le vieillissement de ses cellules, lui infligeant 1D12 dégâts nécrotiques et lui faisant perdre 1D20 PV max. La créature souffre également du statut "Fatigue V".', 20, 1, '-60', false),
('Évocation', 'Au cours d\'un rituel, le lanceur utilise son corps comme un réceptacle pour accueillir l\'âme d\'un défunt. Le lanceur est considéré comme sous l\'influence d\'un contrôle mental, et peut, s\'il le souhaite, effectuer un jet de VOL pour reprendre le contrôle de son corps. L\'esprit du défunt est alors renvoyé dans le Voile. L\'âme de la créature doit également être intacte pour que le sort se conclue. Le lanceur doit également connaître le nom et le visage de la personne.', 10, 0, -60, true),
('Dévocation', 'Le lanceur renvoit un esprit possèdant le corps d\'une créature dans le Voile.', 10, 0, '-x', false);
INSERT INTO `spell` (name, description, level, charge, cost, is_ritual) VALUES
('Globules corrosives I', 'Une aura verte sombre se dégage des mains du lanceur. Une nuée de petites globules se manifestent de cette aura et fondent sur une cible unique, infligeant 1D10 de dégats de corrosion pendant 3 tours. Le sort ne peut se manifester dans un liquide.', 0, 0, '-10', false),
('Globules corrosives II', 'Une aura verte sombre se dégage des mains du lanceur. Une nuée de globules se manifestent de cette aura et fondent sur une cible unique, infligeant 2D10 de dégats de corrosion pendant 3 tours. Le sort ne peut se manifester dans un liquide.', 0, 0, '-30', false),
('Globules corrosives III', 'Une aura verte sombre se dégage des mains du lanceur. Une énorme nuée de globules se manifeste de cette aura et fond sur une cible unique, infligeant 3D10 de dégats de corrosion pendant 3 tours. Le sort ne peut se manifester dans un liquide.', 0, 0, '-50', false),
('Vent toxique', 'Le lanceur manifeste un courant d\'air mordant et nocif sur un petit groupe de cibles, infligeant 4D10 dégâts de corrosion pendant 3 tours à ces dernières.', 20, 1, '-70', false);
-- INGREDIENTS
INSERT INTO `ingredient` (name) VALUES
('Volonté'),
('Geste');
-- VARIABLES
INSERT INTO `variable` (description) VALUES
('Nombre de personnes soignées');
-- ASSOCIATIONS - SPELLS / SCHOOLS
/*
SCHEMA
(id_spell, id_school),
*/
DELIMITER $$
CREATE PROCEDURE insertIntoSchoolRange(IN delimiter_start INT, IN delimiter_end INT, IN school_id INT)
BEGIN
SET @i = delimiter_start;
WHILE @i <= delimiter_end DO
INSERT INTO spells_schools (id_spell, id_school) VALUES (@i, school_id);
SET @i = @i + 1;
END WHILE;
END$$
DELIMITER ;
CALL insertIntoSchoolRange(1, 25, 1);
CALL insertIntoSchoolRange(26, 50, 2);
CALL insertIntoSchoolRange(51, 66, 3);
CALL insertIntoSchoolRange(67, 91, 4);
CALL insertIntoSchoolRange(92, 107, 5);
CALL insertIntoSchoolRange(108, 111, 6);

32
index.js Normal file
View File

@@ -0,0 +1,32 @@
'use strict';
// MODULES
const express = require('express')
const bodyParser = require('body-parser')
const helmet = require('helmet')
const morgan = require('morgan')
const cors = require('cors') // module to format the json response
// Creates instances of database connections
const connection = require('./database/connection')
const db = connection.db
// CONSTANTS
const port = 2814
// Import routes
const routes = require('./routes')
// Builds app w/ express
let app = express()
app.use(bodyParser.json({ limit: '10kb' }))
app.use(cors())
app.use(morgan('tiny'))
app.use(helmet())
// Serves
const server = app.listen( port, () => {console.log(`App listening on port ${port}`)})
// Routing
app.use('/api/spells', routes.spells)
app.use('/api/schools', routes.schools)

13
models/Errors.js Normal file
View File

@@ -0,0 +1,13 @@
// Http error w/ http code
class HttpError extends Error {
constructor(code, message) {
super(message)
this.name = "HttpError"
this.code = code
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = {
HttpError
}

12
models/School.js Normal file
View File

@@ -0,0 +1,12 @@
const School = {
"id": "/SchoolModel",
"type": Object,
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"meta_school": { "type": "number" },
},
"required": ["name", "description"]
}
module.exports = School

39
models/Spell.js Normal file
View File

@@ -0,0 +1,39 @@
const Spell = {
"id": "/SpellModel",
"type": Object,
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"level": { "type": "number" },
"charge": { "type": "number" },
"cost": { "type": "string" },
"is_ritual": { "type": "boolean" },
"schools": {
"type": { "type": "array" },
"items": {
"properties": {
"id": { "type": "number" },
}
}
},
"variables": {
"type": { "type": "array" },
"items": {
"properties": {
"id": { "type": "number" },
}
}
},
"ingredients": {
"type": { "type": "array" },
"items": {
"properties": {
"id": { "type": "number" },
}
}
}
},
"required": ["name", "description"]
}
module.exports = Spell

623
package-lock.json generated Normal file
View File

@@ -0,0 +1,623 @@
{
"name": "auracle-api",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
}
},
"bowser": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz",
"integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA=="
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"camelize": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
"integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-security-policy-builder": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz",
"integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ=="
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"dasherize": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz",
"integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"dns-prefetch-control": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
"integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q=="
},
"dont-sniff-mimetype": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz",
"integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug=="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"expect-ct": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz",
"integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g=="
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"feature-policy": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz",
"integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ=="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"frameguard": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz",
"integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g=="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"helmet": {
"version": "3.22.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-3.22.0.tgz",
"integrity": "sha512-Xrqicn2nm1ZIUxP3YGuTBmbDL04neKsIT583Sjh0FkiwKDXYCMUqGqC88w3NUvVXtA75JyR2Jn6jw6ZEMOD+ZA==",
"requires": {
"depd": "2.0.0",
"dns-prefetch-control": "0.2.0",
"dont-sniff-mimetype": "1.1.0",
"expect-ct": "0.2.0",
"feature-policy": "0.3.0",
"frameguard": "3.1.0",
"helmet-crossdomain": "0.4.0",
"helmet-csp": "2.10.0",
"hide-powered-by": "1.1.0",
"hpkp": "2.0.0",
"hsts": "2.2.0",
"ienoopen": "1.1.0",
"nocache": "2.1.0",
"referrer-policy": "1.2.0",
"x-xss-protection": "1.3.0"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
}
}
},
"helmet-crossdomain": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz",
"integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA=="
},
"helmet-csp": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz",
"integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==",
"requires": {
"bowser": "2.9.0",
"camelize": "1.0.0",
"content-security-policy-builder": "2.1.0",
"dasherize": "2.0.0"
}
},
"hide-powered-by": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz",
"integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg=="
},
"hpkp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz",
"integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI="
},
"hsts": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz",
"integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==",
"requires": {
"depd": "2.0.0"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
}
}
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ienoopen": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz",
"integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ=="
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"jsonschema": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz",
"integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA=="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
},
"mime-types": {
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"requires": {
"mime-db": "1.44.0"
}
},
"morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
"requires": {
"basic-auth": "~2.0.1",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-finished": "~2.3.0",
"on-headers": "~1.0.2"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
}
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mysql": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
"requires": {
"bignumber.js": "9.0.0",
"readable-stream": "2.3.7",
"safe-buffer": "5.1.2",
"sqlstring": "2.3.1"
}
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"nocache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"referrer-policy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz",
"integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA=="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": {
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"serve-static": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
"requires": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.17.1"
}
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"sqlstring": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz",
"integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
},
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
}
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"x-xss-protection": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz",
"integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg=="
}
}
}

37
package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "auracle-api",
"version": "1.0.0",
"description": "API for Auracle database",
"main": "index.js",
"scripts": {
"test": "start"
},
"repository": {
"type": "git",
"url": "git+https://github.com/AlexisNP/spellsaurus.git"
},
"keywords": [
"api",
"rpg",
"spells",
"magic",
"database",
"ttrpg",
"dices",
"worldbuilding"
],
"author": "AlexisNP",
"license": "ISC",
"bugs": {
"url": "https://github.com/AlexisNP/spellsaurus/issues"
},
"homepage": "https://github.com/AlexisNP/spellsaurus#readme",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.17.1",
"helmet": "^3.22.0",
"jsonschema": "^1.2.6",
"morgan": "^1.10.0",
"mysql": "^2.18.1"
}
}

7
routes/index.js Normal file
View File

@@ -0,0 +1,7 @@
const spells = require('./spells')
const schools = require('./schools')
module.exports = {
spells,
schools,
}

136
routes/schools.js Normal file
View File

@@ -0,0 +1,136 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/connection')
const db = connection.db
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const School = require("../models/School")
v.addSchema(School, "/SchoolModel")
// Error handling
const { HttpError } = require('../models/Errors')
// ROUTES
// GET ALL ------------------
const getSchools = () => {
let getSchoolsPromise = new Promise((resolve, reject) => {
let query = "SELECT DISTINCT * FROM school"
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Error: Database error'))
} else if (result.length == 0) {
reject(new HttpError(404, 'Error: No ressource matching this id'))
}
result = await buildSchool(result[0])
resolve(result);
})
})
.catch(err => { throw err })
return getSchoolsPromise
}
router.get('/', async (req, res, next) => {
getSchools()
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(err.message)
})
})
// GET ONE ------------------
const getSchool = (id) => {
let getSchoolPromise = new Promise((resolve, reject) => {
let query = "SELECT * FROM school WHERE id = " + db.escape(id)
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Error: Database error'))
}
result = await buildSchool(result[0])
resolve(result);
})
})
.catch(err => { throw err })
return getSchoolPromise
}
router.get('/:id/', async (req, res, next) => {
getSchool(req.params.id)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(err.message)
})
})
// Param validation for single school
// (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => {
const regex = RegExp(/^[1-9]\d*$/);
try {
if (regex.test(id)) {
next()
} else {
throw new HttpError(403, 'Provided ID must be an integer')
}
} catch (err) {
res.status(err.code).send(err.message)
}
})
// SHARED FUNCTIONS ------------------
// Builds the associated infos for a given school object
const buildSchool = async (school) => {
// Fetches the school's parent school
let fetchMetaSchoolData = new Promise((resolve, reject) => {
let query =
"SELECT ms.id, ms.name " +
"FROM meta_school AS ms " +
"WHERE ms.id = " + school.id_meta_school
db.query(query, (err, result) => {
if (err) {
reject(new HttpError(500, 'Error: Database error'))
} else {
resolve(result);
}
})
})
.catch(err => {
res.status(403).send(err)
})
// Builds the school and returns it
school.meta_school = await fetchMetaSchoolData
return school
}
// Check if spell is null
const isEmptyObject = (obj) => {
if (Object.keys(obj).length === 0 && obj.constructor === Object) {
return true
} else {
return false
}
}
module.exports = router

680
routes/spells.js Normal file
View File

@@ -0,0 +1,680 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/connection')
const db = connection.db
// Validations
const regexInt = RegExp(/^[1-9]\d*$/)
const regexXSS = RegExp(/<[^>]*script/)
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const Spell = require("../models/Spell")
v.addSchema(Spell, "/SpellModel")
// Error handling
const { HttpError } = require('../models/Errors')
// ROUTES
// GET ALL ------------------
const getSpells = () => {
return new Promise((resolve, reject) => {
let query = "SELECT DISTINCT * FROM spell"
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else if (result.length == 0) {
reject(new HttpError(404, 'No spells were found'))
}
// Loops over the results to fetch the associated tables
for (let i = 0; i < result.length; i++) {
try {
result[i] = await buildSpell(result[i])
} catch (err) {
reject(err)
}
}
resolve(result)
})
})
.catch(err => {
throw err
})
}
router.get('/', async (req, res) => {
getSpells()
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// GET ONE ------------------
const getSpell = (id) => {
return new Promise((resolve, reject) => {
let query = "SELECT * FROM spell WHERE id = " + db.escape(id)
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
}
try {
result = buildSpell(result[0])
resolve(result)
} catch (err) {
reject(err)
}
})
})
.catch(err => {
throw err
})
}
router.get('/:id/', async (req, res) => {
getSpell(req.params.id)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// CREATE ONE ------------------
const addSpell = (s) => {
return new Promise(async (resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, Spell).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, Spell).errors))
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
let query =
'INSERT INTO spell (name, description'
if (s.level != undefined) { query += ', level' }
if (s.charge != undefined) { query += ', charge' }
if (s.cost != undefined) { query += ', cost' }
if (s.is_ritual != undefined) { query += ', is_ritual' }
query += `) VALUES (${db.escape(s.name)}, ${db.escape(s.description)}`
if (s.level != undefined) { query += `, ${s.level}` }
if (s.charge != undefined) { query += `, ${s.charge}` }
if (s.cost != undefined) { query += `, ${db.escape(s.cost)}` }
if (s.is_ritual != undefined) { query += `, ${s.is_ritual}` }
query += ')'
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
}
console.log(`Inserted "${s.name}" with ID ${result.insertId}, affecting ${result.affectedRows} row(s)`)
const new_spell_id = result.insertId
let addSchoolsData = () => {
return new Promise((resolve, reject) => {
if (s.schools != undefined) {
if (s.schools.length > 0) {
for (let i = 0; i < s.schools.length; i++) {
if (!regexInt.test(s.schools[i].id)) {
reject(new HttpError(403, 'Query error - School ID should be an integer !'))
}
let insert_schools_query = `INSERT INTO spells_schools (id_spell, id_school) VALUES (${new_spell_id}, ${s.schools[i].id})`
db.query(insert_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - No schools matching this ID'))
} else {
console.log(`Associated school ID ${s.schools[i].id} to spell ID ${new_spell_id}, affecting ${result.affectedRows} row(s)`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let addVariablesData = () => {
return new Promise((resolve, reject) => {
if (s.variables != undefined) {
if (s.variables.length > 0) {
for (let i = 0; i < s.variables.length; i++) {
if (!regexInt.test(s.variables[i].id)) {
reject(new HttpError(403, 'Query error - Variable ID should be an integer !'))
}
let insert_variables_query = `INSERT INTO spells_variables (id_spell, id_variable) VALUES (${new_spell_id}, ${s.variables[i].id})`
db.query(insert_variables_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - No variables matching this ID'))
} else {
console.log(`Associated variable ID ${s.variables[i].id} to spell ID ${new_spell_id}, affecting ${result.affectedRows} row(s)`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let addIngredientsData = () => {
return new Promise((resolve, reject) => {
if (s.ingredients != undefined) {
if (s.ingredients.length > 0) {
for (let i = 0; i < s.ingredients.length; i++) {
if (!regexInt.test(s.ingredients[i].id)) {
reject(new HttpError(403, 'Query error - Ingredient ID should be an integer !'))
}
let insert_ingredients_query = `INSERT INTO spells_ingredients (id_spell, id_ingredient) VALUES (${new_spell_id}, ${s.ingredients[i].id})`
db.query(insert_ingredients_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - No ingredients matching this ID'))
} else {
console.log(`Associated ingredient ID ${s.ingredients[i].id} to spell ID ${new_spell_id}, affecting ${result.affectedRows} row(s)`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
Promise.all([addSchoolsData(), addVariablesData(), addIngredientsData()])
.then(v => {
resolve(getSpell(new_spell_id))
})
.catch(err => {
reject(err)
})
})
}
}).catch(err => {
throw err
})
}
router.post('/', async (req, res) => {
addSpell(req.body)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// UPDATE ONE ------------------
const updateSpell = (s, id) => {
return new Promise( async (resolve, reject) => {
// Check if spell exists
let old_spell = await getSpell(id)
.catch(err => {
reject(err)
})
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, Spell).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, Spell).errors))
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
let query =
'UPDATE spell SET '
if (s.name != undefined) { query += `name = "${s.name}" ` }
if (s.description != undefined) { query += `, description = "${s.description}" ` }
if (s.level != undefined) { query += `, level = ${s.level} ` }
if (s.charge != undefined) { query += `, charge = ${s.charge} ` }
if (s.cost != undefined) { query += `, cost = "${s.cost}" ` }
if (s.is_ritual != undefined) { query += `, is_ritual = ${s.is_ritual} ` }
query += ` WHERE id = ${db.escape(id)}`
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell update failed'))
}
console.log(`Updated "${s.name}" on ID ${old_spell.id}, affecting ${result.affectedRows} row(s)`)
let updateSchoolsData = () => {
return new Promise((resolve, reject) => {
if (s.schools != undefined) {
if (s.schools.length > 0) {
let delete_schools_query =
`DELETE FROM spells_schools WHERE id_spell = ${old_spell.id}`
db.query(delete_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell school deletion failed.'))
}
})
for (let i = 0; i < s.schools.length; i++) {
if (!regexInt.test(s.schools[i].id)) {
reject(new HttpError(403, 'Query error - School ID should be an integer !'))
} else {}
let update_schools_query = `INSERT INTO spells_schools (id_spell, id_school) VALUES (${old_spell.id}, ${s.schools[i].id})`
db.query(update_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - No schools matching this ID'))
} else {
console.log(`Updated association school ID ${s.schools[i].id} to spell ID ${old_spell.id}`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let updateVariablesData = () => {
return new Promise((resolve, reject) => {
if (s.variables != undefined) {
if (s.variables.length > 0) {
let delete_variables_query =
`DELETE FROM spells_variables WHERE id_spell = ${old_spell.id}`
db.query(delete_variables_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell variable deletion failed.'))
}
})
for (let i = 0; i < s.variables.length; i++) {
if (!regexInt.test(s.variables[i].id)) {
reject(new HttpError(403, 'Query error - Variable ID should be an integer !'))
}
let update_variables_query = `INSERT INTO spells_variables (id_spell, id_variable) VALUES (${old_spell.id}, ${s.variables[i].id})`
db.query(update_variables_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - No variables matching this ID'))
} else {
console.log(`Updated variable ID "${s.variables[i].id}" to spell ID ${old_spell.id}`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
let updateIngredientsData = () => {
return new Promise((resolve, reject) => {
if (s.ingredients != undefined) {
if (s.ingredients.length > 0) {
let delete_ingredients_query =
`DELETE FROM spells_ingredients WHERE id_spell = ${old_spell.id}`
db.query(delete_ingredients_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - Spell ingredients deletion failed.'))
}
console.log(result)
})
// Loops over ingredients query
for (let i = 0; i < s.ingredients.length; i++) {
if (!regexInt.test(s.ingredients[i].id)) {
reject(new HttpError(403, 'Query error - Ingredient ID should be an integer !'))
}
let update_ingredients_query = `INSERT INTO spells_ingredients (id_spell, id_ingredient) VALUES (${old_spell.id}, ${s.ingredients[i].id})`
db.query(update_ingredients_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error - No ingredients matching this ID'))
} else {
console.log(`Updated ingredient ID "${s.ingredients[i].id}" to spell ID ${old_spell.id}`)
resolve()
}
})
}
} else {
resolve()
}
} else {
resolve()
}
})
}
const promises = [
updateSchoolsData(),
updateVariablesData(),
updateIngredientsData()
]
Promise.all(promises)
.then(() => {
resolve(getSpell(old_spell.id))
})
.catch(err => {
reject(err)
})
})
}
})
.catch(err => {
throw err
})
}
router.put('/:id/', async (req, res) => {
updateSpell(req.body, req.params.id)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// DELETE ONE ------------------
const deleteSpell = (id) => {
return new Promise(async (resolve, reject) => {
// Check if spell exists
let old_spell = await getSpell(id)
.catch(err => {
reject(err)
})
if (old_spell == undefined) {
reject((new HttpError(404, 'No spells matching this ID')))
} else {
let deleteSchoolsData = () => {
return new Promise((resolve, reject) => {
let delete_schools_query = `DELETE FROM spells_schools WHERE id_spell = ${db.escape(id)}`
db.query(delete_schools_query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Spell schools deletion failed'))
} else {
console.log(`Deleted schools associated to spell ID ${db.escape(id)}`)
resolve()
}
})
})
}
let deleteVariablesData = () => {
return new Promise((resolve, reject) => {
let delete_variables_query = `DELETE FROM spells_variables WHERE id_spell = ${db.escape(id)}`
db.query(delete_variables_query, async (err, result) => {
if (err) {
console.log(err)
reject(new HttpError(500, 'Spell variables deletion failed'))
} else {
console.log(`Deleted variables associated to spell ID ${db.escape(id)}`)
resolve()
}
})
})
}
let deleteIngredientsData = () => {
return new Promise((resolve, reject) => {
let delete_ingredients_query = `DELETE FROM spells_ingredients WHERE id_spell = ${db.escape(id)}`
db.query(delete_ingredients_query, async (err, result) => {
if (err) {
console.log(err)
reject(new HttpError(500, 'Spell ingredients deletion failed'))
} else {
console.log(`Deleted ingredients associated to spell ID ${db.escape(id)}`)
resolve()
}
})
})
}
let deleteSpellData = () => {
return new Promise((resolve, reject) => {
let delete_spell_query = `DELETE FROM spell WHERE id = ${db.escape(id)}`
db.query(delete_spell_query, async (err, result) => {
if (err) {
console.log(err)
reject(new HttpError(500, 'Spell deletion failed'))
} else {
console.log(`Deleted spell ID ${db.escape(id)}, affecting ${result.affectedRows} rows`)
resolve()
}
})
})
}
const promises = [
deleteSchoolsData(),
deleteVariablesData(),
deleteIngredientsData()
]
Promise.all(promises)
.then(() => {
deleteSpellData()
let response = {
message: `Spell ID ${id} was successfully deleted.`
}
resolve(response)
})
.catch(err => {
reject(err)
})
}
})
.catch(err => {
throw err
})
}
router.delete('/:id/', async (req, res) => {
deleteSpell(req.params.id)
.then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send(JSON.stringify(v))
})
.catch(err => {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
})
})
// Param validation for single spell
// (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => {
try {
if (regexInt.test(id)) {
next()
} else {
throw new HttpError(403, 'Provided ID must be an integer and not zero')
}
} catch (err) {
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
}
})
// SHARED FUNCTIONS ------------------
// Builds the associated infos for a given spell object
const buildSpell = async (spell) => {
// Fetches the spell's schools
let fetchSpellSchoolData = (s) => {
return new Promise((resolve, reject) => {
if (s == undefined) { reject(new HttpError(404, "Error: No spells matching this ID"))}
let query =
"SELECT school.id, school.name " +
"FROM spells_schools AS sc " +
"INNER JOIN school AS school ON sc.id_school = school.id " +
"WHERE sc.id_spell = " + s.id
db.query(query, (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else {
s.schools = result
resolve(s)
}
})
})
}
// Fetches the spell's variables
let fetchSpellVariablesData = (s) => {
return new Promise((resolve, reject) => {
if (s == undefined) { reject(new HttpError(404, "Error: No spells matching this ID"))}
let query =
"SELECT variable.id, variable.description " +
"FROM spells_variables AS sv " +
"INNER JOIN variable AS variable ON sv.id_variable = variable.id " +
"WHERE sv.id_spell = " + s.id
db.query(query, (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else {
s.variables = result
resolve(s)
}
})
})
}
// Fetches the spell's ingredients
let fetchSpellIngredientsData = (s) => {
return new Promise((resolve, reject) => {
if (s == undefined) { reject(new HttpError(404, "Error: No spells matching this ID"))}
let query =
"SELECT ingredient.id, ingredient.name " +
"FROM spells_ingredients AS si " +
"INNER JOIN ingredient AS ingredient ON si.id_ingredient = ingredient.id " +
"WHERE si.id_spell = " + s.id
db.query(query, (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else {
s.ingredients = result
resolve(s)
}
})
})
}
// Builds the spell and returns it
let s = await fetchSpellSchoolData(spell)
.then(s => { return fetchSpellVariablesData(s) })
.then(s => { return fetchSpellIngredientsData(s) })
.catch(err => {
throw err
})
return s
}
// Check if spell is null
const isEmptyObject = (obj) => {
if (Object.keys(obj).length === 0 && obj.constructor === Object) {
return true
} else {
return false
}
}
const isXSSAttempt = (string) => {
if (regexXSS.test(string)) {
return true
} else {
return false
}
}
module.exports = router