Merge branch 'dev'

This commit is contained in:
Alexis
2020-06-17 12:29:09 +02:00
76 changed files with 5466 additions and 1730 deletions

2
.gitignore vendored
View File

@@ -21,4 +21,4 @@ yarn-error.log*
*.sw? *.sw?
# creds # creds
/database/credentials.json .env

View File

@@ -2,8 +2,9 @@
A dictionnary of arcane spells from a homebrew tabletop RPG. A dictionnary of arcane spells from a homebrew tabletop RPG.
## Built With ## Built With
* [Vue CLI](http://www.dropwizard.io/1.0.2/docs/) - Web Framework * [Vue CLI](http://www.dropwizard.io/1.0.2/docs/) - Web Framework for the client
* [NPM](https://maven.apache.org/) - Package and Dependency Management * [NPM](https://maven.apache.org/) - Package and Dependency Management
## Authors ## Authors
* **Alexis** - *Designing, Development & Conception* - [Github](https://github.com/AlexisNP) * **Alexis Pelé** - *Designing, Development & Conception* - [Github](https://github.com/AlexisNP)
* **Mathieu Hance** - *Designing, Integration*

View File

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

View File

@@ -1,11 +0,0 @@
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

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

1241
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,14 @@
}, },
"dependencies": { "dependencies": {
"axios": "^0.19.2", "axios": "^0.19.2",
"bootstrap": "^4.5.0",
"bootstrap-vue": "^2.15.0",
"core-js": "^3.6.4", "core-js": "^3.6.4",
"vue": "^2.6.11" "jquery": "^3.5.1",
"popper.js": "^1.16.1",
"v-clipboard": "^2.2.3",
"vue": "^2.6.11",
"vue-masonry": "^0.11.8"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.2.0", "@vue/cli-plugin-babel": "~4.2.0",
@@ -38,7 +44,7 @@
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^6.1.2", "eslint-plugin-vue": "^6.1.2",
"node-sass": "^4.13.1", "node-sass": "^4.14.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"vue-router": "^3.1.6", "vue-router": "^3.1.6",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.11"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

BIN
client/public/img/bg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -4,14 +4,15 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>img/favicon.ico">
<title>Spellsaurus</title> <title>Auracle</title>
<link rel="stylesheet" href="<%= BASE_URL %>libs/style.css">
<script src="<%= BASE_URL %>libs/clipboard.min.js"></script>
</head> </head>
<body> <body>
<noscript> <noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript> </noscript>
<div id="srs"></div> <div id="srs"></div>
<!-- built files will be auto injected -->
</body> </body>
</html> </html>

7
client/public/libs/clipboard.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,68 @@
@import url('https://fonts.googleapis.com/css?family=Playfair+Display:400,400i,500,500i,600,600i,700,700i,800,800i,900,900i&subset=cyrillic,latin-ext');
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@font-face {
font-family: "stgeorge";
src: url("/public/resources/stgeorge.woff2") format("woff2");
font-weight: normal;
font-style: normal;
}
.word-break{
word-wrap: break-word;
}
/*img.emoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}*/
.cursor-pointer{
cursor: pointer;
}
.font-display{ font-family: 'Playfair Display', serif; }
.font-weight-100{ font-weight: 100; }
.font-weight-200{ font-weight: 200; }
.font-weight-300{ font-weight: 300; }
.font-weight-400{ font-weight: 400; }
.font-weight-500{ font-weight: 500; }
.font-weight-600{ font-weight: 600; }
.font-weight-700{ font-weight: 700; }
.font-weight-800{ font-weight: 800; }
.font-weight-900{ font-weight: 900; }
/*.pfp{
background: #f8f9fa;
border-radius: 50%;
}
.pfp-32{ height:32px; width:32px; }
.pfp-64{ height:64px; width:64px; }
.pfp-128{ height:128px; width:128px; }
.pfp-256{ height:256px; width:256px; }*/
.max-540{ max-width: 540px; }
.max-720{ max-width: 720px; }
.max-960{ max-width: 960px; }
.max-1140{ max-width: 1140px; }
.line-height-100{ line-height: 100%; }
@font-face{
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url('/libs/material-design-icons.woff2') format('woff2');
}
.mad{
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
line-height: 1;
transform: translateY(12.5%);
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
body{
background: url('/img/bg1.png') center center fixed repeat;
}

9
client/src/api/api.js Normal file
View File

@@ -0,0 +1,9 @@
import axios from "axios"
const url = process.env.VUE_APP_API_URL
axios.defaults.headers.common[process.env.VUE_APP_API_KEY_NAME] = process.env.VUE_APP_API_KEY_VALUE
export default axios.create({
baseURL: url
})

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
import spellsRepository from './spellsRepository'
import schoolsRepository from './schoolsRepository'
import metaschoolsRepository from './metaschoolsRepository'
import variablesRepository from './variablesRepository'
import ingredientsRepository from './ingredientsRepository'
// List of possible repositories
const repositories = {
spells: spellsRepository,
schools: schoolsRepository,
metaschools: metaschoolsRepository,
variables: variablesRepository,
ingredients: ingredientsRepository,
}
// Usage : RepositoryFactoryInstance.get('reponame');
export const RepositoryFactory = {
get: name => repositories[name]
}

View File

@@ -0,0 +1,17 @@
import api from './api'
// URL for spells
const resource = "/schools"
// CRUD methods for spells
export default {
getSchools() {
return api.get(`${resource}`)
},
getSchool(id) {
return api.get(`${resource}/${id}`)
},
getSpellsFromSchool(id) {
return api.get(`${resource}/${id}/spells`)
}
}

View File

@@ -0,0 +1,23 @@
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}`)
},
addSpell(data) {
return api.post(`${resource}`, data)
},
updateSpell(id, data) {
return api.put(`${resource}/${id}`, data)
},
deleteSpell(id) {
return api.delete(`${resource}/${id}`)
}
}

View File

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

View File

@@ -1,33 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,36 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,36 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -3,14 +3,10 @@
@import '_mixins'; @import '_mixins';
@import '_fonts'; @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 // RESETS
html { html {
box-sizing: border-box;
font-size: $font-size-root; font-size: $font-size-root;
font-family: $font-family; font-family: $font-family !important;
height: 100%; height: 100%;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
line-height: 1.15; line-height: 1.15;
@@ -48,8 +44,6 @@ var {
font-style: normal; font-style: normal;
} }
// BUTTONS
// MISC. // MISC.
hr { hr {
border: none; border: none;

View File

@@ -1,112 +1,33 @@
<template> <template>
<nav class="navbar navbar-dark"> <nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<ul v-if="links.length != 0"> <router-link :to="'/'" class="navbar-brand font-display font-weight-700">Auracle</router-link>
<li v-for="(link, index) in links" :key="index"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<router-link v-bind:to="link.url">{{ link.text }}</router-link> <span class="navbar-toggler-icon"></span>
</li> </button>
</ul> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto" v-if="links.length != 0">
<li class="nav-item" v-for="(link, index) in links" :key="index">
<router-link :to="link.url" class="nav-link">{{ link.text }}</router-link>
</li>
</ul>
</div>
</nav> </nav>
</template> </template>
<script> <script>
export default {
export default { name: 'navbar',
name: 'navbar', data() {
data() { return {
return { links: [
links: [ {
{ text: 'Sortilèges',
id: 0, url: '/spells',
text: 'Accueil', },
url: '/', ]
}, }
{
id: 1,
text: 'Sortilèges',
url: '/spells',
},
{
id: 2,
text: 'Monde',
url: '/world',
},
]
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss"></style>
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,166 @@
<template>
<b-modal
ref="add_spell_modal"
size="lg"
modal-class="b-modal">
<template v-slot:modal-header="{ close }">
<div class="h1 modal-title font-display font-weight-bold" id="spell_show_edit_modal">
<div class="line-height-100">
<span v-if="!spell.name">Nouveau sort</span>
<span v-if="spell.name">{{spell.name}}</span>
</div>
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" @click="close()">
<span aria-hidden="true">&times;</span>
</button>
</template>
<template v-slot:default>
<form id="add-spell" @submit="addSpell">
<div class="form-group">
<label for="spell_name" class="font-weight-bold col-form-label">Nom&nbsp;:</label>
<input type="text" class="form-control" name="spell_name" id="spell_name" placeholder="(256 caractères max.)" v-model="spell.name">
</div>
<div class="form-group">
<label for="spell_description" class="font-weight-bold col-form-label">Description&nbsp;:</label>
<textarea class="form-control" name="spell_description" id="spell_description" placeholder="(2048 caractères max.)" v-model="spell.description"></textarea>
</div>
<div class="form-check form-check-inline">
<input type="checkbox" class="form-check-input" id="spell_ritual" name="spell_ritual" v-model="spell.is_ritual">
<label for="spell_ritual" class="font-weight-bold col-form-label">Rituel ?&nbsp;</label>
</div>
<div class="form-group">
<label for="spell_level" class="font-weight-bold col-form-label">Niveau&nbsp;:</label>
<input type="number" class="form-control" name="spell_level" id="spell_level" min="0" max="100" step="1" placeholder="(Nombre entier de 0 à 100)" v-model="spell.level">
</div>
<div class="form-group">
<label for="spell_schools" class="font-weight-bold col-form-label">École(s)&nbsp;:</label>
<select class="form-control" id="spell_schools" name="spell_schools" multiple v-model="spell.schools">
<option v-for="(school, index) in all_schools" :key="index" :value="'school_' + school.id">{{school.name}}</option>
</select>
</div>
<div class="form-group">
<label for="spell_charge" class="font-weight-bold col-form-label">Charge&nbsp;:</label>
<input type="number" class="form-control" name="spell_charge" id="spell_charge" min="0" max="100" step="1" placeholder="(Nombre entier de 0 à 100)" v-model="spell.charge">
</div>
<div class="form-group">
<label for="spell_ingredients" class="font-weight-bold col-form-label">Ingrédient(s)&nbsp;:</label>
<select class="form-control" id="spell_ingredients" name="spell_ingredients" multiple v-model="spell.ingredients">
<option v-for="(ingredient,index) in all_ingredients" :key="index" :value="'ingredient_' + ingredient.id">{{ingredient.name}}</option>
</select>
</div>
<div class="form-group">
<label for="spell_cost" class="font-weight-bold col-form-label">Coût&nbsp;:</label>
<input type="text" class="form-control" name="spell_cost" id="spell_cost" placeholder="(32 caractères max.)" v-model="spell.cost">
</div>
<div class="form-group">
<label for="spell_variables" class="font-weight-bold col-form-label">Variables(s)&nbsp;:</label>
<select class="form-control" id="spell_variables" name="spell_variables" multiple v-model="spell.variables">
<option v-for="(variable,index) in all_variables" :key="index" :value="'variable_' + variable.id">{{variable.description}}</option>
</select>
</div>
</form>
</template>
<template v-slot:modal-footer="{ close }">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="close()">Fermer</button>
<input type="submit" class="btn btn-primary" value="Enregistrer" form="add-spell">
</template>
</b-modal>
</template>
<script>
// API
import { RepositoryFactory } from "~/api/repositories"
const Spells = RepositoryFactory.get('spells')
const Schools = RepositoryFactory.get('schools')
const Variables = RepositoryFactory.get('variables')
const Ingredients = RepositoryFactory.get('ingredients')
export default {
'name': 'add-spell-card',
data() {
return {
spell: {
type: Object,
name: "",
description: "",
is_ritual: false,
level: 0,
cost: "0",
charge: 0,
schools: [],
variables: [],
ingredients: [],
},
all_schools: [],
all_variables: [],
all_ingredients: [],
}
},
created() {
// Gets all relevant info for multiple selects
let fetchSchools = Schools.getSchools()
let fetchVariables = Variables.getVariables()
let fetchIngredients = Ingredients.getIngredients()
Promise.all([fetchSchools, fetchVariables, fetchIngredients])
.then(v => {
this.all_schools = v[0].data
this.all_variables = v[1].data
this.all_ingredients = v[2].data
})
.catch(err => {
console.log(err)
})
},
mounted() {
this.$refs["add_spell_modal"].show()
this.$root.$on('bv::modal::hide', () => {
this.$emit('cancelAdd')
})
},
methods: {
addSpell(e) {
e.preventDefault()
let schoolsData = Object.values(this.spell.schools).map(v => { return parseInt(v.slice(7)) })
let variablesData = Object.values(this.spell.variables).map(v => { return parseInt(v.slice(9)) })
let ingredientsData = Object.values(this.spell.ingredients).map(v => { return parseInt(v.slice(11)) })
let data = {
name: this.spell.name,
description: this.spell.description,
is_ritual: !!+this.spell.is_ritual,
level: parseInt(this.spell.level),
cost: this.spell.cost,
charge: parseInt(this.spell.charge),
schools: schoolsData,
variables: variablesData,
ingredients: ingredientsData,
}
console.log(data)
Spells.addSpell(data)
.then(v => {
this.$emit('addSpell', v.data)
this.$refs["add_spell_modal"].hide()
})
.catch(err => {
console.log(err)
})
}
}
}
</script>
<style lang="scss" scoped>
textarea, select {
min-height: 10rem;
max-height: 20rem;
}
</style>

View File

@@ -0,0 +1,199 @@
<template>
<b-modal
ref="edit_spell_modal"
size="lg"
modal-class="b-modal"
:spell="computeSpell">
<template v-slot:modal-header="{ close }">
<div class="h1 modal-title font-display font-weight-bold" id="spell_show_edit_modal"><div class="line-height-100">{{spell.name}}#{{spell.id}}</div></div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" @click="close()">
<span aria-hidden="true">&times;</span>
</button>
</template>
<template v-slot:default>
<form id="update-spell" @submit="updateSpell">
<div class="form-group">
<label for="spell_name" class="font-weight-bold col-form-label">Nom&nbsp;:</label>
<input type="text" class="form-control" name="spell_name" id="spell_name" placeholder="(256 caractères max.)" v-model="spell.name">
</div>
<div class="form-group">
<label for="spell_description" class="font-weight-bold col-form-label">Description&nbsp;:</label>
<textarea class="form-control" name="spell_description" id="spell_description" placeholder="(2048 caractères max.)" v-model="spell.description"></textarea>
</div>
<div class="form-check form-check-inline">
<input type="checkbox" class="form-check-input" id="spell_ritual" name="spell_ritual" v-model="spell.is_ritual">
<label for="spell_ritual" class="font-weight-bold col-form-label">Rituel ?&nbsp;</label>
</div>
<div class="form-group">
<label for="spell_level" class="font-weight-bold col-form-label">Niveau&nbsp;:</label>
<input type="number" class="form-control" name="spell_level" id="spell_level" min="0" max="100" step="1" placeholder="(Nombre entier de 0 à 100)" v-model="spell.level">
</div>
<div class="form-group">
<label for="spell_schools" class="font-weight-bold col-form-label">École(s)&nbsp;:</label>
<select class="form-control" id="spell_schools" name="spell_schools" multiple v-model="spell.spell_school_ids_value">
<option v-for="(school, index) in all_schools" :key="index" :value="'school_' + school.id">{{school.name}}</option>
</select>
</div>
<div class="form-group">
<label for="spell_charge" class="font-weight-bold col-form-label">Charge&nbsp;:</label>
<input type="number" class="form-control" name="spell_charge" id="spell_charge" min="0" max="100" step="1" placeholder="(Nombre entier de 0 à 100)" v-model="spell.charge">
</div>
<div class="form-group">
<label for="spell_ingredients" class="font-weight-bold col-form-label">Ingrédient(s)&nbsp;:</label>
<select class="form-control" id="spell_ingredients" name="spell_ingredients" multiple v-model="spell.spell_ingredient_ids_value">
<option v-for="(ingredient,index) in all_ingredients" :key="index" :value="'ingredient_' + ingredient.id">{{ingredient.name}}</option>
</select>
</div>
<div class="form-group">
<label for="spell_cost" class="font-weight-bold col-form-label">Coût&nbsp;:</label>
<input type="text" class="form-control" name="spell_cost" id="spell_cost" placeholder="(32 caractères max.)" v-model="spell.cost">
</div>
<div class="form-group">
<label for="spell_variables" class="font-weight-bold col-form-label">Variables(s)&nbsp;:</label>
<select class="form-control" id="spell_variables" name="spell_variables" multiple v-model="spell.spell_variable_ids_value">
<option v-for="(variable,index) in all_variables" :key="index" :value="'variable_' + variable.id">{{variable.description}}</option>
</select>
</div>
</form>
</template>
<template v-slot:modal-footer="{ close }">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="close()">Fermer</button>
<input type="submit" class="btn btn-primary" value="Enregistrer" form="update-spell">
</template>
</b-modal>
</template>
<script>
// API
import { RepositoryFactory } from "~/api/repositories"
const Spells = RepositoryFactory.get('spells')
const Schools = RepositoryFactory.get('schools')
const Variables = RepositoryFactory.get('variables')
const Ingredients = RepositoryFactory.get('ingredients')
export default {
name: 'edit-spell-card',
props: {
spell: {
type: Object,
required: true,
id: Number,
name: String,
description: String,
is_ritual: Boolean,
schools: Array,
variables: Array,
ingredients: Array,
},
},
data() {
return {
all_schools: [],
all_variables: [],
all_ingredients: [],
}
},
computed: {
computeSpell() {
let output = this.spell
output.spell_school_ids = []
output.spell_variable_ids = []
output.spell_ingredient_ids = []
output.spell_school_ids_value = []
output.spell_variable_ids_value = []
output.spell_ingredient_ids_value = []
output.schools.forEach(element => {
output.spell_school_ids.push(element['id'])
})
output.variables.forEach(element => {
output.spell_variable_ids.push(element['id'])
})
output.ingredients.forEach(element => {
output.spell_ingredient_ids.push(element['id'])
})
output.spell_school_ids.forEach(element => {
output.spell_school_ids_value.push("school_" + element)
})
output.spell_variable_ids.forEach(element => {
output.spell_variable_ids_value.push("variable_" + element)
})
output.spell_ingredient_ids.forEach(element => {
output.spell_ingredient_ids_value.push("ingredient_" + element)
})
return output
}
},
created() {
// Gets all relevant info for multiple selects
let fetchSchools = Schools.getSchools()
let fetchVariables = Variables.getVariables()
let fetchIngredients = Ingredients.getIngredients()
Promise.all([fetchSchools, fetchVariables, fetchIngredients])
.then(v => {
this.all_schools = v[0].data
this.all_variables = v[1].data
this.all_ingredients = v[2].data
})
.catch(err => {
console.log(err)
})
},
mounted() {
this.$refs["edit_spell_modal"].show()
this.$root.$on('bv::modal::hide', () => {
this.$emit('editSpell', {})
})
},
watch: {
computeSpell: {
deep: true,
handler() {
this.$refs["edit_spell_modal"].show()
}
}
},
methods: {
updateSpell(e) {
e.preventDefault()
let schoolsData = Object.values(this.spell.spell_school_ids_value).map(v => { return parseInt(v.slice(7)) })
let variablesData = Object.values(this.spell.spell_variable_ids_value).map(v => { return parseInt(v.slice(9)) })
let ingredientsData = Object.values(this.spell.spell_ingredient_ids_value).map(v => { return parseInt(v.slice(11)) })
let data = {
name: this.spell.name,
description: this.spell.description,
is_ritual: !!+this.spell.is_ritual,
level: parseInt(this.spell.level),
cost: this.spell.cost,
charge: parseInt(this.spell.charge),
schools: schoolsData,
variables: variablesData,
ingredients: ingredientsData,
}
Spells.updateSpell(this.spell.id, data)
.then(v => {
this.$emit('updateSpell', v.data)
})
.catch(err => {
console.log(err)
})
},
}
}
</script>
<style lang="scss" scoped>
textarea, select {
min-height: 10rem;
max-height: 20rem;
}
</style>

View File

@@ -1,208 +1,135 @@
<template> <template>
<article class="spell-card"> <div
<div class="content"> :class="main_school"
<header> class="col-12 col-sm-6 col-lg-4 col-xl-3 mb-4 grid-item grid-sizer">
<h3>{{ name }}</h3> <div class="spellcard bg-white p-4 rounded text-dark shadow" style="border-left-width:4px;border-left-style:solid;">
</header>
<section class="casting-header"> <div :title="spell.name" class="h3 font-display font-weight-bold text-wrap word-break" style="line-height:100%;">
<div class="casting-infos"> <a class="text-decoration-none" href="#">{{spell.name}}</a>
<div class="type"> </div>
<div class="level">
<div class="label"> <div>
<span>LV.</span> <div class="font-weight-700 text-muted d-inline-block">Niveau {{spell.level}}</div>
<br> <span> · </span>
<span>MIN</span> <div class="text-muted d-inline-block">
</div> <span v-for="(school,index) in spell.schools" :key="index">
<var>{{ level }}</var> <span v-if="index!=0">, </span>
</div> <a :href="'/school/'+school.id" class="text-secondary">{{school.name}}</a>
<ul class="schools"> </span>
<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>
<div class="casting-ingredients"> </div>
<p class="title">Nécessite :</p>
<p class="content"> <div v-if="spell.charge!=0" class="small font-weight-bold">
<span v-for="(ingredient, index) in ingredients" :key="index"> <span>Charge {{spell.charge}} tour(s)</span>
{{ ingredient.name }}<span v-if="index != Object.keys(ingredients).length - 1">, </span> </div>
<div v-if="spell.ingredients.length>0" class="small">
<span class="font-weight-bold">Nécessite</span>
<span v-for="(ingredient,index) in spell.ingredients" :key="index">
<span v-if="index!=0">, </span>
<span>{{ingredient.name}}</span>
</span>
</div>
<div
v-clipboard="spell.description"
:id="'spell_description_' + spell.id"
v-b-tooltip.click
placement="bottom"
title="Description copiée !"
class="small text-muted font-italic mt-2">
<span>{{spell.description}}</span>
</div>
<div class="mt-2">
<div class="font-weight-bold d-inline-block"><span>Coût </span>{{spell.cost}}</div>
<div v-if="spell.variables.length>0" class="small d-inline-block">, &nbsp;:</div>
<div class=small>
<span v-for="(variable,index) in spell.variables" :key="index">
<span class="font-weight-bold">
<span v-if="index!=0"><br></span>
<span>{{String.fromCharCode(120+index)}}</span>
</span> </span>
</p> <span> = {{variable.description}}</span>
</span>
</div> </div>
</section> <div class="text-right">
<hr> <a class="h5 text-secondary mr-1">
<section class="spell-description"> <i class="mad" @click="editSpell(spell)">edit</i>
<p>{{ description }}</p> </a>
</section> <a class="h5 text-danger">
<hr> <i class="mad" @click="deleteSpell(spell)">delete</i>
<footer v-if="variables"> </a>
<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> </div>
</footer> </div>
</div> </div>
</article> </div>
</template> </template>
<script> <script>
// API
import { RepositoryFactory } from "~/api/repositories"
const Spells = RepositoryFactory.get('spells')
export default { export default {
name: 'spell-card', name: 'spell-card',
props: { props: {
name: String, spell: Object,
description: String,
level: Number,
charge: Number,
cost: String,
schools: Array,
ingredients: Array,
variables: Array,
}, },
data() { data() {
return { return {
isFlipped: false, main_school: this.spell.schools[0].name,
} }
}, },
created() {
this.main_school = this.main_school.toLowerCase();
},
methods: { methods: {
toggleCard: async function() { editSpell(spell) {
this.isFlipped = !this.isFlipped; this.$emit('editSpell', spell)
},
deleteSpell(spell) {
Spells.deleteSpell(this.spell.id)
.then(() => {
this.$emit('deleteSpell', spell)
})
} }
}, },
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.spell-card {
.spell-card { @mixin colorschool($sname,$scolor) {
max-width: 35rem; &.#{$sname} {
padding: .5rem; .spellcard { border-left-color: $scolor; }
margin: .5rem auto .5rem; .h3>a { color: $scolor; }
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;
}
}
}
}
}
} }
@include colorschool(lumomancie,#babaa4);
@include colorschool(vitamancie,#57ab6e);
@include colorschool(obstrumancie,#bd4a66);
@include colorschool(tenebromancie,#404842);
@include colorschool(necromancie,#5d4777);
@include colorschool(morbomancie,#d8733d);
@include colorschool(pyromancie,#b6362a);
@include colorschool(hydromancie,#3f68c7);
@include colorschool(electromancie,#cd9731);
@include colorschool(terramancie,#7e5540);
@include colorschool(caelomancie,#a8a8a8);
@include colorschool(légimancie,#5dbabd);
@include colorschool(illusiomancie,#9f63a1);
@include colorschool(cruciomancie,#252451);
@include colorschool(chronomancie,#79896a);
@include colorschool(spatiomancie,#2d4776);
@include colorschool(kénomancie,#101010);
@include colorschool(lutomancie,#4e2827);
@include colorschool(échomancie,#6d9fd1);
@include colorschool(protomancie,#4f5751);
@include colorschool(rebumancie,#8e7245);
@include colorschool(vocamancie,#247864);
@include colorschool(somamancie,#976c67);
@include colorschool(antimancie,#ad95c1);
}
</style> </style>

View File

@@ -1,26 +1,44 @@
<template> <template>
<div class="spells-list"> <div class="spell-list-wrapper">
<spell-card v-for="(spell, index) in spells" :key="index" v-bind="spell"/> <button type="button" class="btn font-display font-weight-bold btn-lg btn-outline-dark btn-block shadow-sm mb-4" @click="showAdd">
<i class="mad">add</i>
<span>Ajouter un sort</span>
</button>
<div v-masonry transition-duration=".5s" item-selector=".spell-card" class="row spells-list">
<spell-card v-masonry-tile class="spell-card" v-for="(spell) in spells" :key="spell.id" v-bind:spell="spell" @editSpell="editSpell" @deleteSpell="deleteSpell"/>
</div>
<edit-spell-card v-if="Object.keys(current_edit_spell).length > 0" v-bind:spell="current_edit_spell" @editSpell="editSpell" @updateSpell="updateSpell"/>
<add-spell-card v-if="adding_spell" @cancelAdd="cancelAdd" @addSpell="addSpell"/>
</div> </div>
</template> </template>
<script> <script>
// Components // Components
import spellcard from "./spell-card" import SpellCard from "./spell-card"
import EditSpellCard from "./edit-spell-card"
import AddSpellCard from "./add-spell-card"
// API // API
import { RepositoryFactory } from "../../../api/repositories" import { RepositoryFactory } from "~/api/repositories"
const spellsRepository = RepositoryFactory.get('spells') const Spells = RepositoryFactory.get('spells')
const Schools = RepositoryFactory.get('schools')
export default { export default {
name: 'spellslist', name: 'spellslist',
components: { components: {
'spell-card': spellcard, 'spell-card': SpellCard,
'edit-spell-card': EditSpellCard,
'add-spell-card': AddSpellCard,
},
props: {
school_id: String
}, },
data() { data() {
return { return {
loading: false, loading: false,
spells: [] spells: [],
current_edit_spell: {},
adding_spell: false,
} }
}, },
created() { created() {
@@ -28,20 +46,55 @@ export default {
}, },
methods: { methods: {
async fetchSpells() { async fetchSpells() {
const { data } = await spellsRepository.getSpells() if (!this.school_id) {
return data const { data } = await Spells.getSpells()
return data
} else {
const { data } = await Schools.getSpellsFromSchool(this.school_id)
return data.spells
}
}, },
async computeSpells() { async computeSpells() {
this.loading = true this.loading = true
const displaySpells = await this.fetchSpells(); const displaySpells = await this.fetchSpells()
this.loading = false this.loading = false
this.spells = displaySpells.slice(0, 10) this.spells = displaySpells
},
editSpell(e) {
this.current_edit_spell = e;
},
showAdd() {
this.adding_spell = true
},
cancelAdd() {
this.adding_spell = false
},
addSpell(e) {
Spells.getSpell(e.id)
.then(v => {
this.spells.unshift(v.data)
})
.then(() => {
this.adding_spell = false
})
.catch(err => {
console.log(err)
})
},
updateSpell(e) {
let oldSpell = this.spells.find(x => x.id === e.id)
this.spells.splice(this.spells.indexOf(oldSpell), 1, e)
this.current_edit_spell = {}
},
deleteSpell(e) {
this.spells.splice(this.spells.indexOf(e), 1)
this.current_edit_spell = {}
} }
} }
} }
</script> </script>
<style> <style lang="scss">
</style> </style>

View File

@@ -1,15 +1,54 @@
// Core
import Vue from 'vue' import Vue from 'vue'
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
import App from './app.vue' import App from './app.vue'
// Auth
require('dotenv').config()
// Router
import routes from './routes' import routes from './routes'
Vue.use(VueRouter)
// Jquery
import jquery from 'jquery'
window.$ = jquery
window.jquery = jquery
// Global styles
import './assets/scss/_global.scss'
// Bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import 'bootstrap/dist/js/bootstrap.js'
import { BootstrapVue } from 'bootstrap-vue'
Vue.use(BootstrapVue)
// Masonry
import { VueMasonryPlugin } from 'vue-masonry';
Vue.use(VueMasonryPlugin)
// Clipboard plugin
import clipboard from 'v-clipboard'
Vue.use(clipboard)
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.use(VueRouter)
var filter = function(text, length, clamp){
clamp = clamp || '...';
var node = document.createElement('div');
node.innerHTML = text;
var content = node.textContent;
return content.length > length ? content.slice(0, length) + clamp : content;
};
Vue.filter('truncate', filter);
const router = new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',
routes, routes,
linkActiveClass: "",
linkExactActiveClass: "active",
}); });
const app = new Vue({ const app = new Vue({

View File

@@ -1,18 +0,0 @@
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

@@ -1,15 +1,14 @@
<template> <template>
<div class="home-page"> <div class="container-fluid" id="index-container">
<section class="landing"> <section class="fullpage">
<div class="splash-title"> <div class="px-4">
<h1>Auracle</h1> <h1 class="text-dark mb-4 font-display line-height-100 font-weight-900">"C'est magique, ta gueule."</h1>
<div> <div class="lead font-display font-weight-bold"> N'importe quel MJ, une fois dans sa vie</div>
<p>Recueil de savoirs arcaniques et ésotériques des contrées lointaines de Léïm</p> <hr class="w-50">
<p>Découvrez ses sortilèges et maléfices...</p> <div class="lead"><span class="font-display" style="font-weight:700;">Auracle</span> est une base de données publique en ligne pour sortilèges, en soutien au jeu de rôle sur table <span class="font-weight-bold text-secondary">Leïm</span>.</div>
</div> </div>
</div> </section>
</section> </div>
</div>
</template> </template>
<script> <script>
@@ -19,39 +18,20 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.home-page { .fullpage {
.landing { min-height: calc(100vh - 56px);
min-height: 100vh; display: flex;
display: flex; justify-content: center;
justify-content: center; align-items: center;
align-items: center; h1 {
background: $dull--black; font-size: 5rem;
}
.splash-title { }
padding-bottom: 1rem; @media only screen and (max-width: 600px) {
color: $white; .fullpage {
text-align: center; h1 {
@include blue-glow-text(.8); font-size: 3.5rem;
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> </style>

View File

@@ -0,0 +1,48 @@
<template>
<div class="container-fluid p-4" id="spell-container">
<h1 class="display-3 font-display mb-3">{{ school.name }}</h1>
<p>{{ school.description }}</p>
<spell-list :school_id="id"/>
</div>
</template>
<script>
import SpellsList from "~/components/spells/spells-list"
// API
import { RepositoryFactory } from "~/api/repositories"
const Schools = RepositoryFactory.get('schools')
export default {
name: 'single-school',
components: {
'spell-list': SpellsList,
},
data() {
return {
loading: false,
school: {},
id: this.$route.params.id
}
},
created() {
this.computeSchool()
},
methods: {
async fetchSchool(id) {
const { data } = await Schools.getSchool(id)
return data
},
async computeSchool() {
this.loading = true
const displaySchool = await this.fetchSchool(this.id)
this.loading = false
this.school = displaySchool
},
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -1,23 +1,48 @@
<template> <template>
<section class="spells-page"> <div class="container-fluid p-4" id="spell-container">
<spell-list/> <h1 class="display-3 font-display mb-3">Sortilèges</h1>
</section> <div class="mb-4">
<form>
<div class="form-group mb-2">
<input type="text" class="form-control" name="search_terms" id="search_terms" placeholder="Rechercher un sort">
</div>
<div class="mb-3">
<div class="form-check form-check-inline">
<input checked class="form-check-input" type="checkbox" id="search_fields_name" value="search_fields_name">
<label class="form-check-label" for="search_fields_name">Nom</label>
</div>
<div class="form-check form-check-inline">
<input checked class="form-check-input" type="checkbox" id="search_fields_schools" value="search_fields_schools">
<label class="form-check-label" for="search_fields_schools">École(s)</label>
</div>
<div class="form-check form-check-inline">
<input checked class="form-check-input" type="checkbox" id="search_fields_ingredients" value="search_fields_ingredients">
<label class="form-check-label" for="search_fields_ingredients">Ingrédients</label>
</div>
<div class="form-check form-check-inline">
<input checked class="form-check-input" type="checkbox" id="search_fields_description" value="search_fields_description">
<label class="form-check-label" for="search_fields_description">Description</label>
</div>
<div class="form-check form-check-inline">
<input checked class="form-check-input" type="checkbox" id="search_fields_variables" value="search_fields_variables">
<label class="form-check-label" for="search_fields_variables">Variables</label>
</div>
</div>
</form>
</div>
<spell-list/>
</div>
</template> </template>
<script> <script>
import SpellsList from "../components/spells/spells-list"
// Components export default {
import spellslist from "../components/spells/spells-list"
export default {
name: 'spells', name: 'spells',
components: { components: {
'spell-list': spellslist, 'spell-list': SpellsList,
} }
} }
</script> </script>
<style> <style lang="scss"></style>
</style>

View File

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

View File

@@ -1,10 +1,7 @@
// App router
// Pages // Pages
import index from "./pages/index-page"; import Index from "./pages/index-page";
import spells from "./pages/spells-page"; import Spells from "./pages/spells-page";
import world from "./pages/world-page"; import SchoolSingle from "./pages/schools/single-school-page";
// Routes // Routes
const routes = [ const routes = [
@@ -12,13 +9,13 @@ const routes = [
path: "*", redirect: "/", path: "*", redirect: "/",
}, },
{ {
path: '/', component: index, path: '/', component: Index,
}, },
{ {
path: '/spells', component: spells, path: '/spells', component: Spells,
}, },
{ {
path: '/world', component: world, path: '/school/:id', component: SchoolSingle, props: true
} }
]; ];

View File

@@ -1,14 +1,11 @@
const path = require('path')
module.exports = { module.exports = {
css: { configureWebpack: {
loaderOptions: { resolve: {
sass: { alias: {
prependData: ` "~": path.resolve(__dirname, 'src/')
@import "@/assets/scss/_global.scss"; }
@import "@/assets/scss/_variables.scss";
@import "@/assets/scss/_mixins.scss";
@import "@/assets/scss/_fonts.scss";
`
}
} }
} }
}; };

View File

@@ -1,6 +1,6 @@
DROP DATABASE IF EXISTS spellsaurus; DROP DATABASE IF EXISTS auracle;
CREATE DATABASE spellsaurus CHARACTER SET utf8 COLLATE utf8_bin; CREATE DATABASE auracle CHARACTER SET utf8 COLLATE utf8_bin;
USE spellsaurus; USE auracle;
/* ==== PRIMARY TABLES ==== */ /* ==== PRIMARY TABLES ==== */
@@ -29,15 +29,16 @@ CREATE TABLE IF NOT EXISTS `school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école", `name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école",
`description` VARCHAR(255) DEFAULT "Description de l'école", `description` VARCHAR(255) DEFAULT "Description de l'école",
`id_meta_school` INT UNSIGNED NOT NULL, `meta_school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
FOREIGN KEY(`id_meta_school`) REFERENCES meta_school(`id`) FOREIGN KEY(`meta_school_id`) REFERENCES meta_school(`id`)
); );
/* COMMON INGREDIENTS */ /* COMMON INGREDIENTS */
CREATE TABLE IF NOT EXISTS `ingredient` ( CREATE TABLE IF NOT EXISTS `ingredient` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre", `name` VARCHAR(255) NOT NULL DEFAULT "Langue de salamandre",
`description` VARCHAR(255) NOT NULL DEFAULT "Une langue de salamandre de feu qui bouge encore un peu.",
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
@@ -48,35 +49,45 @@ CREATE TABLE IF NOT EXISTS `variable` (
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
/* ==== USER TABLES ==== */
CREATE TABLE IF NOT EXISTS `user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT "Disciple",
`mail` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`banned` BOOLEAN DEFAULT false,
PRIMARY KEY(`id`)
);
/* ==== ASSOCIATION TABLES ==== */ /* ==== ASSOCIATION TABLES ==== */
/* SPELLS' SCHOOLS */ /* SPELLS' SCHOOLS */
/* One spell can have multiple (up to 3) schools */ /* One spell can have multiple (up to 3) schools */
CREATE TABLE IF NOT EXISTS `spells_schools` ( CREATE TABLE IF NOT EXISTS `spell_school` (
`id_spell` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`id_school` INT UNSIGNED NOT NULL, `school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_school`), PRIMARY KEY (`spell_id`, `school_id`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`), FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`id_school`) REFERENCES school(`id`) FOREIGN KEY(`school_id`) REFERENCES school(`id`)
); );
/* SPELLS' VARIABLES */ /* SPELLS' VARIABLES */
/* One spell can have multiple (up to 2) variables of cost */ /* One spell can have multiple (up to 2) variables of cost */
CREATE TABLE IF NOT EXISTS `spells_variables` ( CREATE TABLE IF NOT EXISTS `spell_variable` (
`id_spell` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`id_variable` INT UNSIGNED NOT NULL, `variable_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_variable`), PRIMARY KEY (`spell_id`, `variable_id`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`), FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`id_variable`) REFERENCES variable(`id`) FOREIGN KEY(`variable_id`) REFERENCES variable(`id`)
); );
/* SPELLS' VARIABLES */ /* SPELLS' VARIABLES */
/* One spell can have multiple ingredients */ /* One spell can have multiple ingredients */
CREATE TABLE IF NOT EXISTS `spells_ingredients` ( CREATE TABLE IF NOT EXISTS `spell_ingredient` (
`id_spell` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`id_ingredient` INT UNSIGNED NOT NULL, `ingredient_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id_spell`, `id_ingredient`), PRIMARY KEY (`spell_id`, `ingredient_id`),
FOREIGN KEY(`id_spell`) REFERENCES spell(`id`), FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`id_ingredient`) REFERENCES ingredient(`id`) FOREIGN KEY(`ingredient_id`) REFERENCES ingredient(`id`)
); );

View File

@@ -1,5 +1,5 @@
SET NAMES utf8; SET NAMES utf8;
USE spellsaurus; USE auracle;
-- META SCHOOLS -- META SCHOOLS
INSERT INTO `meta_school` (name, description) VALUES INSERT INTO `meta_school` (name, description) VALUES
@@ -12,7 +12,7 @@ INSERT INTO `meta_school` (name, description) VALUES
('Magies autres', 'Magies trop spécifiques et ne rentrant dans aucune autre grande école.'); ('Magies autres', 'Magies trop spécifiques et ne rentrant dans aucune autre grande école.');
-- SCHOOLS -- SCHOOLS
INSERT INTO `school` (name, description, id_meta_school) VALUES INSERT INTO `school` (name, description, meta_school_id) VALUES
('Lumomancie', 'Discipline arcanique de la lumière.', 1), ('Lumomancie', 'Discipline arcanique de la lumière.', 1),
('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 1), ('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 1),
('Obstrumancie', 'Discipline arcanique de la protection et des sceaux.', 1), ('Obstrumancie', 'Discipline arcanique de la protection et des sceaux.', 1),
@@ -202,10 +202,14 @@ INSERT INTO `spell` (name, description, level, charge, cost, is_ritual) VALUES
('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), ('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); ('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);
-- USERS
INSERT INTO `user` (name, mail, password) VALUES
('Kevin', '35.alexis.mail@gmail.com', 'sept777');
-- INGREDIENTS -- INGREDIENTS
INSERT INTO `ingredient` (name) VALUES INSERT INTO `ingredient` (name, description) VALUES
('Volonté'), ('Volonté', 'La force de volonté du lanceur, concentrée sur un objectif'),
('Geste'); ('Geste', 'Un geste précis facilitant la canalisation magique');
-- VARIABLES -- VARIABLES
INSERT INTO `variable` (description) VALUES INSERT INTO `variable` (description) VALUES
@@ -215,14 +219,15 @@ INSERT INTO `variable` (description) VALUES
/* /*
SCHEMA SCHEMA
(id_spell, id_school), (id_spell, id_school),
*/ */
DELIMITER $$ DELIMITER $$
CREATE PROCEDURE insertIntoSchoolRange(IN delimiter_start INT, IN delimiter_end INT, IN school_id INT) CREATE PROCEDURE insertIntoSchoolRange(IN delimiter_start INT, IN delimiter_end INT, IN id_school INT)
BEGIN BEGIN
SET @i = delimiter_start; SET @i = delimiter_start;
WHILE @i <= delimiter_end DO WHILE @i <= delimiter_end DO
INSERT INTO spells_schools (id_spell, id_school) VALUES (@i, school_id); INSERT INTO spell_school (spell_id, school_id) VALUES (@i, id_school);
SET @i = @i + 1; SET @i = @i + 1;
END WHILE; END WHILE;
END$$ END$$

18
database/bookshelf.js Normal file
View File

@@ -0,0 +1,18 @@
// MODULES
const fs = require('fs')
const mysql = require('mysql')
// Setting up the database connection
const knex = require('knex')({
client: "mysql",
connection: {
host : process.env.DB_HOST,
user : process.env.DB_USER,
password : process.env.DB_PASSWORD,
database : "auracle",
charset : "utf8"
},
})
const bookshelf = require('bookshelf')(knex)
module.exports = { bookshelf }

View File

@@ -1,22 +0,0 @@
// 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 }

View File

@@ -1,7 +0,0 @@
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

@@ -6,9 +6,10 @@ const bodyParser = require('body-parser')
const helmet = require('helmet') const helmet = require('helmet')
const morgan = require('morgan') const morgan = require('morgan')
const cors = require('cors') // module to format the json response const cors = require('cors') // module to format the json response
const dotenv = require('dotenv').config()
// Creates instances of database connections // Creates instances of database connections
const connection = require('./database/connection') const connection = require('./database/bookshelf')
const db = connection.db const db = connection.db
// CONSTANTS // CONSTANTS
@@ -27,6 +28,33 @@ app.use(helmet())
// Serves // Serves
const server = app.listen( port, () => {console.log(`App listening on port ${port}`)}) const server = app.listen( port, () => {console.log(`App listening on port ${port}`)})
// Get credentials
// app.get('/api/login', (req, res, next) => {
// if (req.headers.auracle_key !== process.env.API_KEY_PUBLIC) {
// return res.status(401).send('Credentials error !')
// } else {
// return res.status(200).send(JSON.stringify(
// {
// "secret_key": process.env.API_KEY_PRIVATE,
// })
// )
// }
// })
// Auth guard
const authguard = (req, res, next) => {
if (req.headers.auracle_key !== process.env.API_KEY_PUBLIC) {
return res.status(401).send('The API key is either missing or incorrect.')
} else {
next()
}
}
// Routing // Routing
app.use('/api/spells', routes.spells) app.use('/api/spells', authguard,routes.spells)
app.use('/api/schools', routes.schools) app.use('/api/schools', authguard, routes.schools)
app.use('/api/meta_schools', authguard, routes.meta_schools)
app.use('/api/variables', authguard, routes.variables)
app.use('/api/ingredients', authguard, routes.ingredients)
app.use('/api/users', authguard, routes.users)

View File

@@ -0,0 +1,13 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./spell-model')
let Ingredient = bookshelf.Model.extend({
tableName: 'ingredient',
spells() {
return this.belongsToMany( 'Spell', 'spell_ingredient')
}
})
module.exports = bookshelf.model('Ingredient', Ingredient)

View File

@@ -0,0 +1,13 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./school-model')
let MetaSchool = bookshelf.Model.extend({
tableName: 'meta_school',
schools() {
return this.hasMany( 'School' )
}
})
module.exports = bookshelf.model('MetaSchool', MetaSchool)

17
models/school-model.js Normal file
View File

@@ -0,0 +1,17 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./spell-model')
require('./meta-school-model')
let School = bookshelf.Model.extend({
tableName: 'school',
spells() {
return this.belongsToMany( 'Spell', 'spell_school' )
},
meta_schools() {
return this.belongsTo( 'MetaSchool', 'meta_school_id' )
}
})
module.exports = bookshelf.model('School', School)

21
models/spell-model.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./school-model')
require('./variable-model')
require('./ingredient-model')
let Spell = bookshelf.Model.extend({
tableName: 'spell',
schools() {
return this.belongsToMany( 'School', 'spell_school' )
},
variables() {
return this.belongsToMany( 'Variable', 'spell_variable' )
},
ingredients() {
return this.belongsToMany( 'Ingredient', 'spell_ingredient' )
}
})
module.exports = bookshelf.model('Spell', Spell)

8
models/user-model.js Normal file
View File

@@ -0,0 +1,8 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
let User = bookshelf.Model.extend({
tableName: 'user',
})
module.exports = bookshelf.model('User', User)

13
models/variable-model.js Normal file
View File

@@ -0,0 +1,13 @@
'use strict'
const bookshelf = require('../database/bookshelf').bookshelf
require('./spell-model')
let Variable = bookshelf.Model.extend({
tableName: 'variable',
spells() {
return this.belongsToMany( 'Spell', 'spell_variable')
}
})
module.exports = bookshelf.model('Variable', Variable)

1805
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,10 +27,15 @@
}, },
"homepage": "https://github.com/AlexisNP/spellsaurus#readme", "homepage": "https://github.com/AlexisNP/spellsaurus#readme",
"dependencies": { "dependencies": {
"bcrypt": "^4.0.1",
"bookshelf": "^1.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"helmet": "^3.22.0", "helmet": "^3.22.0",
"jsonschema": "^1.2.6", "jsonschema": "^1.2.6",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1" "mysql": "^2.18.1"
} }

View File

@@ -0,0 +1,188 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/ingredient-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const IngredientValidation = require("../validations/IngredientValidation")
v.addSchema(IngredientValidation, "/IngredientValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class IngredientRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get ingredients"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['spells']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get ingredient"))
})
})
}
getSpellsFromOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get ingredient"))
})
})
}
addOne(igr) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(igr)) {
reject(new HttpError(403, "Error: Ingredient cannot be nothing !"))
} else if (!v.validate(igr, IngredientValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors))
} else if (this.isXSSAttempt(igr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
bookshelf.transaction(t => {
return model.forge({
'name': igr.name,
'description': igr.description,
}).save(null, {
transacting: t
})
.catch(err => {
throw err
})
})
.then(v => {
return v.load(['spells'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Insert Ingredient error"))
})
}
})
}
updateOne(id, igr) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(igr)) {
reject(new HttpError(403, "Error: Ingredient cannot be nothing !"))
} else if (!v.validate(igr, IngredientValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors))
} else if (this.isXSSAttempt(igr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
model.forge({id: id})
.fetch({require: true, withRelated: ['spells']})
.then(v => {
bookshelf.transaction(t => {
return v.save({
'name': igr.name,
'description': igr.description,
}, {
method: 'update',
transacting: t
})
.catch(err => {
console.log(err)
throw err
})
})
.then(v => {
return v.load(['spells'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Update Ingredient error"))
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get ingredient"))
})
}
})
}
deleteOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({require: true, withRelated: ['spells']})
.then(v => {
v.spells().detach()
v.destroy()
})
.then(() => {
resolve({
'message': 'Ingredient with ID ' + id + ' successfully deleted !'
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get ingredient"))
})
})
}
// Check if object is null
isEmptyObject(obj) {
if (Object.keys(obj).length === 0 && obj.constructor === Object) {
return true
} else {
return false
}
}
// Check if script injection attempt
isXSSAttempt(string) {
if (regexXSS.test(string)) {
return true
} else {
return false
}
}
}
module.exports = IngredientRepository

View File

@@ -0,0 +1,53 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/meta-school-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const MetaSchoolValidation = require("../validations/MetaSchoolValidation")
v.addSchema(MetaSchoolValidation, "/MetaSchoolValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class MetaSchoolRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get meta schools"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['schools']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get meta school"))
})
})
}
}
module.exports = MetaSchoolRepository

View File

@@ -0,0 +1,189 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/school-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const SchoolValidation = require("../validations/SchoolValidation")
v.addSchema(SchoolValidation, "/SchoolValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class SchoolRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['meta_schools'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get schools"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['meta_schools']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get school"))
})
})
}
getSpellsFromOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get school"))
})
})
}
addOne(s) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) {
reject(new HttpError(403, "Error: School cannot be nothing !"))
} else if (!v.validate(s, SchoolValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors))
} else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
bookshelf.transaction(t => {
return model.forge({
'name': s.name,
'description': s.description,
'meta_school_id': s.meta_school_id,
}).save(null, {
transacting: t
})
.catch(err => {
throw err
})
})
.then(v => {
return v.load(['meta_schools'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Insert School error"))
})
}
})
}
updateOne(id, s) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) {
reject(new HttpError(403, "Error: School cannot be nothing !"))
} else if (!v.validate(s, SchoolValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors))
} else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
model.forge({id: id})
.fetch({require: true, withRelated: ['meta_schools']})
.then(v => {
bookshelf.transaction(t => {
return v.save({
'name': s.name,
'description': s.description,
'meta_school_id': s.meta_school_id
}, {
method: 'update',
transacting: t
})
.catch(err => {
console.log(err)
throw err
})
})
.then(v => {
return v.load(['meta_schools'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Update School error"))
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get school"))
})
}
})
}
deleteOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({require: true, withRelated: ['spells', 'meta_schools']})
.then(v => {
v.spells().detach()
v.destroy()
})
.then(() => {
resolve({
'message': 'School with ID ' + id + ' successfully deleted !'
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get school"))
})
})
}
// Check if object is null
isEmptyObject(obj) {
if (Object.keys(obj).length === 0 && obj.constructor === Object) {
return true
} else {
return false
}
}
// Check if script injection attempt
isXSSAttempt(string) {
if (regexXSS.test(string)) {
return true
} else {
return false
}
}
}
module.exports = SchoolRepository

View File

@@ -0,0 +1,245 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/spell-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const SpellValidation = require("../validations/SpellValidation")
v.addSchema(SpellValidation, "/SpellValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class SpellRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get spells"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['schools.meta_schools', 'variables', 'ingredients']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get spell"))
})
})
}
addOne(s) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, SpellValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).errors))
} else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description) || this.isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
bookshelf.transaction(t => {
return model.forge({
'name': s.name,
'description': s.description,
'level': s.level,
'charge' : s.charge,
'cost' : s.cost,
'is_ritual' : s.is_ritual
}).save(null, {
transacting: t
})
.tap(spell => {
return spell
.schools()
.attach(s.schools, {
transacting: t
});
})
.tap(spell => {
return spell
.variables()
.attach(s.variables, {
transacting: t
});
})
.tap(spell => {
return spell
.ingredients()
.attach(s.ingredients, {
transacting: t
});
})
.catch(err => {
throw err
})
})
.then(v => {
return v.load(['schools.meta_schools', 'variables', 'ingredients'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Insert Spell error"))
})
}
})
}
updateOne(id, s) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, SpellValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).errors))
} else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description) || this.isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
model.forge({id: id})
.fetch({require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients']})
.then(v => {
bookshelf.transaction(t => {
return v.save({
'name': s.name,
'description': s.description,
'level': s.level,
'charge' : s.charge,
'cost' : s.cost,
'is_ritual' : s.is_ritual
}, {
method: 'update',
transacting: t
})
// Detaches AND attaches pivot tables, dw about it
.tap(spell => {
if (s.schools) {
let schools = spell.related('school');
return spell.schools().detach(schools, { transacting: t});
}
return
})
.tap(spell => {
if (s.variables) {
let variables = spell.related('variable');
return spell.variables().detach(variables, { transacting: t});
}
return
})
.tap(spell => {
if (s.ingredients) {
let ingredients = spell.related('ingredient');
return spell.ingredients().detach(ingredients, { transacting: t});
}
})
.tap(spell => {
return spell
.schools()
.attach(s.schools, {
transacting: t
});
})
.tap(spell => {
return spell
.variables()
.attach(s.variables, {
transacting: t
});
})
.tap(spell => {
return spell
.ingredients()
.attach(s.ingredients, {
transacting: t
});
})
.catch(err => {
console.log(err)
throw err
})
})
.then(v => {
return v.load(['schools.meta_schools', 'variables', 'ingredients'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Update Spell error"))
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get spell"))
})
}
})
}
deleteOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients']})
.then(v => {
v.schools().detach()
v.variables().detach()
v.ingredients().detach()
v.destroy()
})
.then(() => {
resolve({
'message': 'Spell with ID ' + id + ' successfully deleted !'
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get spell"))
})
})
}
// Check if object is null
isEmptyObject(obj) {
if (Object.keys(obj).length === 0 && obj.constructor === Object) {
return true
} else {
return false
}
}
// Check if script injection attempt
isXSSAttempt(string) {
if (regexXSS.test(string)) {
return true
} else {
return false
}
}
}
module.exports = SpellRepository

View File

@@ -0,0 +1,56 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/user-model')
// Hashing and passwords
const bcrypt = require('bcrypt')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const UserValidation = require("../validations/UserValidation")
v.addSchema(UserValidation, "/UserValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class UserRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
this._model.forge()
.fetchAll()
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get users"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
this._model.forge()
.where({ 'id' : id })
.fetch()
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get user"))
})
})
}
}
module.exports = UserRepository

View File

@@ -0,0 +1,185 @@
'use strict'
// Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/variable-model')
// Model validation
const Validator = require('jsonschema').Validator
const v = new Validator()
const VariableValidation = require("../validations/VariableValidation")
v.addSchema(VariableValidation, "/VariableValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class VariableRepository {
constructor() {
}
getAll() {
return new Promise((resolve, reject) => {
model.forge()
.fetchAll({ withRelated: ['spells'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get variables"))
})
})
}
getOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['spells']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get variable"))
})
})
}
getSpellsFromOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools']})
.then(v => {
resolve(v.toJSON({ omitPivot: true }))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Couldn't get variable"))
})
})
}
addOne(vr) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(vr)) {
reject(new HttpError(403, "Error: Variable cannot be nothing !"))
} else if (!v.validate(vr, VariableValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors))
} else if (this.isXSSAttempt(vr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
bookshelf.transaction(t => {
return model.forge({
'description': vr.description,
}).save(null, {
transacting: t
})
.catch(err => {
throw err
})
})
.then(v => {
return v.load(['spells'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Insert Variable error"))
})
}
})
}
updateOne(id, vr) {
return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(vr)) {
reject(new HttpError(403, "Error: Variable cannot be nothing !"))
} else if (!v.validate(vr, VariableValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors))
} else if (this.isXSSAttempt(vr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
model.forge({id: id})
.fetch({require: true, withRelated: ['spells']})
.then(v => {
bookshelf.transaction(t => {
return v.save({
'description': vr.description,
}, {
method: 'update',
transacting: t
})
.catch(err => {
console.log(err)
throw err
})
})
.then(v => {
return v.load(['spells'])
})
.then(v => {
resolve(this.getOne(v.id))
})
.catch(err => {
console.log(err)
reject(new HttpError(500, "Update Variable error"))
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get variable"))
})
}
})
}
deleteOne(id) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'id' : id })
.fetch({require: true, withRelated: ['spells']})
.then(v => {
v.spells().detach()
v.destroy()
})
.then(() => {
resolve({
'message': 'Variable with ID ' + id + ' successfully deleted !'
})
})
.catch(err => {
console.log(err)
reject(new HttpError(404, "Couldn't get variable"))
})
})
}
// Check if object is null
isEmptyObject(obj) {
if (Object.keys(obj).length === 0 && obj.constructor === Object) {
return true
} else {
return false
}
}
// Check if script injection attempt
isXSSAttempt(string) {
if (regexXSS.test(string)) {
return true
} else {
return false
}
}
}
module.exports = VariableRepository

View File

@@ -1,7 +1,15 @@
const spells = require('./spells') const spells = require('./spells')
const schools = require('./schools') const schools = require('./schools')
const meta_schools = require('./meta_schools')
const variables = require('./variables')
const ingredients = require('./ingredients')
const users = require('./users')
module.exports = { module.exports = {
spells, spells,
schools, schools,
meta_schools,
ingredients,
variables,
users,
} }

190
routes/ingredients.js Normal file
View File

@@ -0,0 +1,190 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/bookshelf')
const db = connection.db
// Repository
const IngredientRepository = require('../repositories/ingredient-repository');
const Ingredients = new IngredientRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getIngredients = () => {
return Ingredients.getAll()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getIngredients()
.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 getIngredient = (id) => {
return Ingredients.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/', async (req, res) => {
getIngredient(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
})
)
})
})
// GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => {
return Ingredients.getSpellsFromOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/spells', async (req, res) => {
getSpellsFromOne(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 addIngredient = (igr) => {
return Ingredients.addOne(igr)
.catch(err => {
console.log(err)
throw err
})
}
router.post('/', async (req, res) => {
addIngredient(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 updateIngredient = (id, igr) => {
return Ingredients.updateOne(id, igr)
.catch(err => {
console.log(err)
throw err
})
}
router.put('/:id/', async (req, res) => {
updateIngredient(req.params.id, 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
})
)
})
})
// DELETE ONE ------------------
const deleteIngredient = (id) => {
return Ingredients.deleteOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.delete('/:id/', async (req, res) => {
deleteIngredient(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 ID
// (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => {
try {
if (regexInt.test(id)) {
next()
} else {
throw new Error;
}
} catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero')
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
}
})
module.exports = router

91
routes/meta_schools.js Normal file
View File

@@ -0,0 +1,91 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/bookshelf')
const db = connection.db
// Repository
const MetaSchoolRepository = require('../repositories/meta-school-repository');
const MetaSchools = new MetaSchoolRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getMetaSchools = () => {
return MetaSchools.getAll()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getMetaSchools()
.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 getMetaSchool = (id) => {
return MetaSchools.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/', async (req, res) => {
getMetaSchool(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
})
)
})
})
// Param validation for ID
// (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => {
try {
if (regexInt.test(id)) {
next()
} else {
throw new Error;
}
} catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero')
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
}
})
module.exports = router

View File

@@ -5,132 +5,185 @@ const express = require('express')
let router = express.Router() let router = express.Router()
// Connection // Connection
const connection = require('../database/connection') const connection = require('../database/bookshelf')
const db = connection.db const db = connection.db
// Model validation // Repository
const Validator = require('jsonschema').Validator const SchoolRepository = require('../repositories/school-repository');
const v = new Validator() const Schools = new SchoolRepository();
const School = require("../models/School")
v.addSchema(School, "/SchoolModel") const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling // Error handling
const { HttpError } = require('../models/Errors') const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getSchools = () => { const getSchools = () => {
let getSchoolsPromise = new Promise((resolve, reject) => { return Schools.getAll()
.catch(err => {
let query = "SELECT DISTINCT * FROM school" console.log(err)
throw err
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) => { router.get('/', async (req, res) => {
getSchools() getSchools()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v)) res.end(JSON.stringify(v))
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(err.message) res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
}) })
}) })
// GET ONE ------------------ // GET ONE ------------------
const getSchool = (id) => { const getSchool = (id) => {
let getSchoolPromise = new Promise((resolve, reject) => { return Schools.getOne(id)
.catch(err => {
let query = "SELECT * FROM school WHERE id = " + db.escape(id) console.log(err)
throw err
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) => { router.get('/:id/', async (req, res) => {
getSchool(req.params.id) getSchool(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.end(JSON.stringify(v)) res.end(JSON.stringify(v))
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(err.message) res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
}) })
}) })
// 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 ------------------ // GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => {
// Builds the associated infos for a given school object return Schools.getSpellsFromOne(id)
const buildSchool = async (school) => { .catch(err => {
console.log(err)
// Fetches the school's parent school throw err
let fetchMetaSchoolData = new Promise((resolve, reject) => { })
let query = }
"SELECT ms.id, ms.name " + router.get('/:id/spells', async (req, res) => {
"FROM meta_school AS ms " + getSpellsFromOne(req.params.id)
"WHERE ms.id = " + school.id_meta_school .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8')
db.query(query, (err, result) => { res.end(JSON.stringify(v))
if (err) {
reject(new HttpError(500, 'Error: Database error'))
} else {
resolve(result);
}
})
}) })
.catch(err => { .catch(err => {
res.status(403).send(err) res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
}) })
})
// Builds the school and returns it
school.meta_school = await fetchMetaSchoolData // CREATE ONE ------------------
return school const addSchool = (s) => {
return Schools.addOne(s)
.catch(err => {
console.log(err)
throw err
})
} }
router.post('/', async (req, res) => {
addSchool(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
})
)
})
})
// Check if spell is null // UPDATE ONE ------------------
const isEmptyObject = (obj) => { const updateSchool = (id, s) => {
if (Object.keys(obj).length === 0 && obj.constructor === Object) { return Schools.updateOne(id, s)
return true .catch(err => {
} else { console.log(err)
return false throw err
})
}
router.put('/:id/', async (req, res) => {
updateSchool(req.params.id, 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
})
)
})
})
// DELETE ONE ------------------
const deleteSchool = (id) => {
return Schools.deleteOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.delete('/:id/', async (req, res) => {
deleteSchool(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 ID
// (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => {
try {
if (regexInt.test(id)) {
next()
} else {
throw new Error;
}
} catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero')
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
} }
} })
module.exports = router module.exports = router

View File

@@ -5,48 +5,24 @@ const express = require('express')
let router = express.Router() let router = express.Router()
// Connection // Connection
const connection = require('../database/connection') const connection = require('../database/bookshelf')
const db = connection.db const db = connection.db
// Validations // Repository
const regexInt = RegExp(/^[1-9]\d*$/) const SpellReposity = require('../repositories/spell-repository');
const regexXSS = RegExp(/<[^>]*script/) const Spells = new SpellReposity();
// Model validation const regexInt = RegExp(/^[1-9]\d*$/)
const Validator = require('jsonschema').Validator
const v = new Validator()
const Spell = require("../models/Spell")
v.addSchema(Spell, "/SpellModel")
// Error handling // Error handling
const { HttpError } = require('../models/Errors') const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getSpells = () => { const getSpells = () => {
return new Promise((resolve, reject) => { return Spells.getAll()
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 => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -69,23 +45,9 @@ router.get('/', async (req, res) => {
// GET ONE ------------------ // GET ONE ------------------
const getSpell = (id) => { const getSpell = (id) => {
return new Promise((resolve, reject) => { return Spells.getOne(id)
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 => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -108,136 +70,9 @@ router.get('/:id/', async (req, res) => {
// CREATE ONE ------------------ // CREATE ONE ------------------
const addSpell = (s) => { const addSpell = (s) => {
return new Promise(async (resolve, reject) => { return Spells.addOne(s)
.catch(err => {
// Checks if body exists and if the model fits, and throws errors if it doesn't console.log(err)
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 throw err
}) })
} }
@@ -259,176 +94,15 @@ router.post('/', async (req, res) => {
// UPDATE ONE ------------------ // UPDATE ONE ------------------
const updateSpell = (s, id) => { const updateSpell = (id, s) => {
return new Promise( async (resolve, reject) => { return Spells.updateOne(id, s)
// 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 => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
router.put('/:id/', async (req, res) => { router.put('/:id/', async (req, res) => {
updateSpell(req.body, req.params.id) updateSpell(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
res.send(JSON.stringify(v)) res.send(JSON.stringify(v))
@@ -446,96 +120,9 @@ router.put('/:id/', async (req, res) => {
// DELETE ONE ------------------ // DELETE ONE ------------------
const deleteSpell = (id) => { const deleteSpell = (id) => {
return new Promise(async (resolve, reject) => { return Spells.deleteOne(id)
// 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 => { .catch(err => {
console.log(err)
throw err throw err
}) })
} }
@@ -556,16 +143,17 @@ router.delete('/:id/', async (req, res) => {
}) })
// Param validation for single spell // Param validation for ID
// (check if id is int) (could be refactored) // (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => { router.param('id', (req, res, next, id) => {
try { try {
if (regexInt.test(id)) { if (regexInt.test(id)) {
next() next()
} else { } else {
throw new HttpError(403, 'Provided ID must be an integer and not zero') throw new Error;
} }
} catch (err) { } catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero')
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
{ {
"error": err.message, "error": err.message,
@@ -575,106 +163,4 @@ router.param('id', (req, res, next, id) => {
} }
}) })
// 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 module.exports = router

192
routes/users.js Normal file
View File

@@ -0,0 +1,192 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/bookshelf')
const db = connection.db
// Repository
const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getUsers = () => {
return Users.getAll()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getUsers()
.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 getUser = (id) => {
return Users.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/', async (req, res) => {
getUser(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 addUser = (u) => {
return new Promise(async (resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't
if (isEmptyObject(u)) {
reject(new HttpError(403, "Error: User cannot be empty !"))
} else if (!v.validate(u, User).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(u, User).errors))
} else if (isXSSAttempt(u.name) || isXSSAttempt(u.mail) || isXSSAttempt(u.password)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else {
let query = `INSERT INTO user (name, mail, password) VALUES (${db.escape(u.name)}, ${db.escape(u.mail)}, ${db.escape(u.password)})`
db.query(query, async (err, result) => {
if (err) {
reject(new HttpError(500, 'Database error'))
} else {
console.log(`Inserted "${u.name}" with ID ${result.insertId}, affecting ${result.affectedRows} row(s)`)
const new_user_id = result.insertId
let response = {
"message": `User created successfully !`,
"inserted_id": `${new_user_id}`
}
resolve(response)
}
})
}
}).catch(err => {
throw err
})
}
router.post('/', async (req, res) => {
addUser(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
})
)
})
})
// DELETE ONE ------------------
const deleteUser = (id) => {
return new Promise(async (resolve, reject) => {
// Check if user exists
let old_user = await getUser(id)
.catch(() => {
reject((new HttpError(404, 'No user matching this ID')))
})
console.log(old_user)
let deleteUserData = () => {
return new Promise((resolve, reject) => {
let delete_user_query = `DELETE FROM user WHERE id = ${db.escape(id)}`
db.query(delete_user_query, async (err, result) => {
if (err) {
console.log(err)
reject(new HttpError(500, 'Spell deletion failed'))
} else {
let response = {
"message": "User delete successully",
"deleted_id": `${id}`
}
resolve(response)
}
})
})
}
deleteUserData()
.then(v => {
resolve(v)
})
.catch(err => {
reject(err)
})
})
.catch(err => {
throw err
})
}
router.delete('/:id/', async (req, res) => {
deleteUser(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 user
// (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => {
try {
if (regexInt.test(id)) {
next()
} else {
new Error
}
} catch (err) {
throw new HttpError(403, 'Provided ID must be an integer and not zero')
}
})
module.exports = router

190
routes/variables.js Normal file
View File

@@ -0,0 +1,190 @@
'use strict'
// Router
const express = require('express')
let router = express.Router()
// Connection
const connection = require('../database/bookshelf')
const db = connection.db
// Repository
const VariableRepository = require('../repositories/variable-repository');
const Variables = new VariableRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES
// GET ALL ------------------
const getvariables = () => {
return Variables.getAll()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getvariables()
.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 getVariable = (id) => {
return Variables.getOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/', async (req, res) => {
getVariable(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
})
)
})
})
// GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => {
return Variables.getSpellsFromOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/:id/spells', async (req, res) => {
getSpellsFromOne(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 addVariable = (vr) => {
return Variables.addOne(vr)
.catch(err => {
console.log(err)
throw err
})
}
router.post('/', async (req, res) => {
addVariable(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 updateVariable = (id, vr) => {
return Variables.updateOne(id, vr)
.catch(err => {
console.log(err)
throw err
})
}
router.put('/:id/', async (req, res) => {
updateVariable(req.params.id, 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
})
)
})
})
// DELETE ONE ------------------
const deleteVariable = (id) => {
return Variables.deleteOne(id)
.catch(err => {
console.log(err)
throw err
})
}
router.delete('/:id/', async (req, res) => {
deleteVariable(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 ID
// (check if id is int) (could be refactored)
router.param('id', (req, res, next, id) => {
try {
if (regexInt.test(id)) {
next()
} else {
throw new Error;
}
} catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero')
res.status(err.code).send(JSON.stringify(
{
"error": err.message,
"code": err.code
})
)
}
})
module.exports = router

View File

@@ -0,0 +1,11 @@
const Ingredient = {
"id": "/IngredientValidation",
"type": Object,
"properties": {
"name": { "type": "string" },
"description": { "type": "string" }
},
"required": ["name", "description"]
}
module.exports = Ingredient

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
const Spell = { const Spell = {
"id": "/SpellModel", "id": "/SpellValidation",
"type": Object, "type": Object,
"properties": { "properties": {
"name": { "type": "string" }, "name": { "type": "string" },

View File

@@ -0,0 +1,13 @@
const User = {
"id": "/UserValidation",
"type": Object,
"properties": {
"name": { "type": "string" },
"mail": { "type": "string" },
"password": { "type": "string" },
"banned": { "type": "boolean"},
},
"required": ["name", "password", "mail"]
}
module.exports = User

View File

@@ -0,0 +1,10 @@
const Variable = {
"id": "/VariableValidation",
"type": Object,
"properties": {
"description": { "type": "string" },
},
"required": ["description"]
}
module.exports = Variable