Merge pull request #13 from AlexisNP/dev

Added query options for spell routes
This commit is contained in:
AlexisNP
2020-08-04 21:21:12 +02:00
committed by GitHub
56 changed files with 1440 additions and 911 deletions

View File

@@ -11960,6 +11960,11 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
}, },
"vue-cookies": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/vue-cookies/-/vue-cookies-1.7.0.tgz",
"integrity": "sha512-vuEUm6wYMMrFAHFCrkzIUAy8+MgPAbBGmYXnk2M6X6O2KHbMT1wuDD2izacmsSUp6ZM02e23MJRtPRobl88VMg=="
},
"vue-eslint-parser": { "vue-eslint-parser": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz",
@@ -12068,6 +12073,11 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "dev": true
}, },
"vuex": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
"integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw=="
},
"watchpack": { "watchpack": {
"version": "1.7.2", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.2.tgz",

View File

@@ -22,9 +22,7 @@
"url": "git+https://github.com/AlexisNP/spellsaurus.git" "url": "git+https://github.com/AlexisNP/spellsaurus.git"
}, },
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve"
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"axios": "^0.19.2", "axios": "^0.19.2",
@@ -35,7 +33,9 @@
"popper.js": "^1.16.1", "popper.js": "^1.16.1",
"v-clipboard": "^2.2.3", "v-clipboard": "^2.2.3",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-masonry": "^0.11.8" "vue-cookies": "^1.7.0",
"vue-masonry": "^0.11.8",
"vuex": "^3.5.1"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.2.0", "@vue/cli-plugin-babel": "~4.2.0",

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -4,10 +4,10 @@
<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 %>img/favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="preload" as="font" href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap" crossorigin="anonymous">
<link rel="preload" as="font" href="https://fonts.googleapis.com/css?family=Playfair+Display:400,400i,500,500i,600,600i,700,700i,800,800i,900,900i&subset=cyrillic,latin-ext" crossorigin="anonymous">
<title>Auracle</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>

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import schoolsRepository from './schoolsRepository'
import metaschoolsRepository from './metaschoolsRepository' import metaschoolsRepository from './metaschoolsRepository'
import variablesRepository from './variablesRepository' import variablesRepository from './variablesRepository'
import ingredientsRepository from './ingredientsRepository' import ingredientsRepository from './ingredientsRepository'
import usersRepository from './usersRepository'
// List of possible repositories // List of possible repositories
const repositories = { const repositories = {
@@ -11,6 +12,7 @@ const repositories = {
metaschools: metaschoolsRepository, metaschools: metaschoolsRepository,
variables: variablesRepository, variables: variablesRepository,
ingredients: ingredientsRepository, ingredients: ingredientsRepository,
users: usersRepository,
} }
// Usage : RepositoryFactoryInstance.get('reponame'); // Usage : RepositoryFactoryInstance.get('reponame');

View File

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

View File

@@ -1,23 +1,24 @@
import api from './api' import api from './api'
// URL for spells
const resource = "/spells" const resource = "/spells"
// CRUD methods for spells
export default { export default {
getSpells() { getAll() {
return api.get(`${resource}`) return api.get(`${resource}`)
}, },
getSpell(id) { getPage(page) {
return api.get(`${resource}/page/${page}`)
},
getOne(id) {
return api.get(`${resource}/${id}`) return api.get(`${resource}/${id}`)
}, },
addSpell(data) { addOne(data) {
return api.post(`${resource}`, data) return api.post(`${resource}`, data)
}, },
updateSpell(id, data) { updateOne(id, data) {
return api.put(`${resource}/${id}`, data) return api.put(`${resource}/${id}`, data)
}, },
deleteSpell(id) { deleteOne(id) {
return api.delete(`${resource}/${id}`) return api.delete(`${resource}/${id}`)
} }
} }

View File

@@ -0,0 +1,12 @@
import api from './api'
const resource = "/users"
export default {
login(data) {
return api.post(`${resource}/login`, data)
},
register(data) {
return api.post(`${resource}`, data)
},
}

View File

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

View File

@@ -1,28 +1,21 @@
<template> <template>
<div id="srs"> <div id="srs">
<header> <header>
<navigation/> <navbar/>
</header> </header>
<router-view/> <router-view/>
</div> </div>
</template> </template>
<script> <script>
import navbar from "./components/global/navbar/navbar";
export default { export default {
name: 'app', name: 'app'
components: {
'navigation': navbar
} }
}
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
#srs {
min-height: 100vh;
background: url('./assets/images/bg1.png') center center fixed repeat;
}
</style> </style>

View File

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 228 KiB

View File

@@ -0,0 +1,36 @@
.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; }
.line-height-100{ line-height: 100%; }
@font-face{
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url('../fonts/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';
font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}

View File

@@ -1,6 +1,5 @@
// Other global files // Other global files
@import '_variables'; @import '_variables';
@import '_mixins';
@import '_fonts'; @import '_fonts';
// RESETS // RESETS
@@ -13,29 +12,21 @@ html {
color: $primary--base; color: $primary--base;
-ms-text-size-adjust: 100%; -ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
}
html *,
html *::before,
html *::after {
box-sizing: border-box;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
outline: none; outline: none;
box-sizing: border-box;
&:before, &:after {
box-sizing: border-box;
}
}
} }
@at-root { @at-root {
@-ms-viewport { width: device-width; } @-ms-viewport { width: device-width; }
} }
a {
text-decoration: none;
color: inherit;
}
ul { ul {
list-style-type: none; list-style-type: none;
} }
@@ -44,10 +35,28 @@ var {
font-style: normal; font-style: normal;
} }
// MISC.
hr { hr {
border: none; border: none;
border-top-style: solid; border-top-style: solid;
border-top-width: 1px; border-top-width: 1px;
border-top-color: rgba($black, .25); border-top-color: rgba($black, .25);
} }
// BOOTSTRAP
.nav-link {
cursor: pointer;
}
.btn {
margin-right: 5px;
}
.word-break{ word-wrap: break-word; }
.cursor-pointer{ cursor: pointer; }
// LAYOUTS
.fullpage {
min-height: calc(100vh - 56px);
display: flex;
justify-content: center;
align-items: center;
}

View File

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

View File

@@ -0,0 +1,110 @@
<template>
<div class="lds-roller">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</template>
<script>
export default {
name: 'loader',
data() {
return {
}
}
}
</script>
<style scoped lang="scss">
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
&:after {
content: "";
display: block;
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--primary);
margin: -4px 0 0 -4px;
}
&:nth-child(1) {
animation-delay: -0.036s;
&:after {
top: 63px;
left: 63px;
}
}
&:nth-child(2) {
animation-delay: -0.072s;
&:after {
top: 68px;
left: 56px;
}
}
&:nth-child(3) {
animation-delay: -0.108s;
&:after {
top: 71px;
left: 48px;
}
}
&:nth-child(4) {
animation-delay: -0.144s;
&:after {
top: 72px;
left: 40px;
}
}
&:nth-child(5) {
animation-delay: -0.18s;
&:after {
top: 71px;
left: 32px;
}
}
&:nth-child(6) {
animation-delay: -0.216s;
&:after {
top: 68px;
left: 24px;
}
}
&:nth-child(7) {
animation-delay: -0.252s;
&:after {
top: 63px;
left: 17px;
}
}
&:nth-child(8) {
animation-delay: -0.288s;
&:after {
top: 56px;
left: 12px;
}
}
}
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,15 +1,22 @@
<template> <template>
<nav class="navbar navbar-expand-sm navbar-dark bg-dark"> <nav class="navbar navbar-expand-sm navbar-dark bg-dark">
<router-link :to="'/'" class="navbar-brand font-display font-weight-700">Auracle</router-link> <router-link :to="'/'" class="navbar-brand font-display font-weight-700">Auracle</router-link>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbar">
<ul class="navbar-nav mr-auto" v-if="links.length != 0"> <ul class="navbar-nav mr-auto" v-if="links.length != 0">
<li class="nav-item" v-for="(link, index) in links" :key="index"> <li class="nav-item" v-for="(link, index) in links" :key="index">
<router-link :to="link.url" class="nav-link">{{ link.text }}</router-link> <router-link :to="link.url" class="nav-link">{{ link.text }}</router-link>
</li> </li>
</ul> </ul>
<div v-if="user" class="navbar-nav">
<router-link :to="'/profil'" class="nav-link">{{ user.name }}</router-link>
<div @click="logoutUser()" class="nav-link">Deconnexion</div>
</div>
<div v-else class="navbar-nav">
<router-link :to="'/connexion'" class="nav-link">Connexion</router-link>
</div>
</div> </div>
</nav> </nav>
</template> </template>
@@ -22,10 +29,26 @@
links: [ links: [
{ {
text: 'Sortilèges', text: 'Sortilèges',
url: '/spells', url: '/sorts',
}, },
{
text: 'Écoles',
url: '/ecoles',
}
] ]
} }
},
computed: {
user() {
return this.$store.state.user
}
},
methods: {
logoutUser() {
this.$cookies.remove('U_')
this.$store.commit('logout')
this.$router.push('/')
}
} }
} }
</script> </script>

View File

@@ -103,9 +103,9 @@ export default {
}, },
created() { created() {
// Gets all relevant info for multiple selects // Gets all relevant info for multiple selects
let fetchSchools = Schools.getSchools() let fetchSchools = Schools.getAll()
let fetchVariables = Variables.getVariables() let fetchVariables = Variables.getAll()
let fetchIngredients = Ingredients.getIngredients() let fetchIngredients = Ingredients.getAll()
Promise.all([fetchSchools, fetchVariables, fetchIngredients]) Promise.all([fetchSchools, fetchVariables, fetchIngredients])
.then(v => { .then(v => {
@@ -143,9 +143,7 @@ export default {
ingredients: ingredientsData, ingredients: ingredientsData,
} }
console.log(data) Spells.addOne(data)
Spells.addSpell(data)
.then(v => { .then(v => {
this.$emit('addSpell', v.data) this.$emit('addSpell', v.data)
this.$refs["add_spell_modal"].hide() this.$refs["add_spell_modal"].hide()

View File

@@ -13,7 +13,7 @@
</template> </template>
<template v-slot:default> <template v-slot:default>
<form id="update-spell" @submit="updateSpell"> <form id="update-spell" ref="update-spell" @submit="updateSpell">
<div class="form-group"> <div class="form-group">
<label for="spell_name" class="font-weight-bold col-form-label">Nom&nbsp;:</label> <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"> <input type="text" class="form-control" name="spell_name" id="spell_name" placeholder="(256 caractères max.)" v-model="spell.name">
@@ -60,7 +60,8 @@
</template> </template>
<template v-slot:modal-footer="{ close }"> <template v-slot:modal-footer="{ close }">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="close()">Fermer</button> <button type="button" class="btn btn-danger" data-dismiss="modal" @click="close()">Fermer</button>
<!-- <input type="button" class="btn btn-success" value="Enregistrer comme nouveau" @click="cloneSpell()"> -->
<input type="submit" class="btn btn-primary" value="Enregistrer" form="update-spell"> <input type="submit" class="btn btn-primary" value="Enregistrer" form="update-spell">
</template> </template>
</b-modal> </b-modal>
@@ -131,9 +132,9 @@ export default {
}, },
created() { created() {
// Gets all relevant info for multiple selects // Gets all relevant info for multiple selects
let fetchSchools = Schools.getSchools() let fetchSchools = Schools.getAll()
let fetchVariables = Variables.getVariables() let fetchVariables = Variables.getAll()
let fetchIngredients = Ingredients.getIngredients() let fetchIngredients = Ingredients.getAll()
Promise.all([fetchSchools, fetchVariables, fetchIngredients]) Promise.all([fetchSchools, fetchVariables, fetchIngredients])
.then(v => { .then(v => {
@@ -160,6 +161,8 @@ export default {
} }
}, },
methods: { methods: {
cloneSpell() {
},
updateSpell(e) { updateSpell(e) {
e.preventDefault() e.preventDefault()
@@ -179,7 +182,7 @@ export default {
ingredients: ingredientsData, ingredients: ingredientsData,
} }
Spells.updateSpell(this.spell.id, data) Spells.updateOne(this.spell.id, data)
.then(v => { .then(v => {
this.$emit('updateSpell', v.data) this.$emit('updateSpell', v.data)
}) })

View File

@@ -5,7 +5,7 @@
<div class="spellcard bg-white p-4 rounded text-dark shadow" style="border-left-width:4px;border-left-style:solid;"> <div class="spellcard bg-white p-4 rounded text-dark shadow" style="border-left-width:4px;border-left-style:solid;">
<div :title="spell.name" class="h3 font-display font-weight-bold text-wrap word-break" style="line-height:100%;"> <div :title="spell.name" class="h3 font-display font-weight-bold text-wrap word-break" style="line-height:100%;">
<a class="text-decoration-none" href="#">{{spell.name}}</a> <router-link :to="`/sorts/${spell.id}`" class="text-decoration-none">{{spell.name}}</router-link>
</div> </div>
<div> <div>
@@ -14,7 +14,7 @@
<div class="text-muted d-inline-block"> <div class="text-muted d-inline-block">
<span v-for="(school,index) in spell.schools" :key="index"> <span v-for="(school,index) in spell.schools" :key="index">
<span v-if="index!=0">, </span> <span v-if="index!=0">, </span>
<a :href="'/school/'+school.id" class="text-secondary">{{school.name}}</a> <router-link :to="`ecoles/${school.id}`" class="text-secondary">{{school.name}}</router-link>
</span> </span>
</div> </div>
</div> </div>
@@ -23,6 +23,10 @@
<span>Charge {{spell.charge}} tour(s)</span> <span>Charge {{spell.charge}} tour(s)</span>
</div> </div>
<div v-if="spell.is_ritual" class="small font-weight-bold">
<span>Rituel</span>
</div>
<div v-if="spell.ingredients.length>0" class="small"> <div v-if="spell.ingredients.length>0" class="small">
<span class="font-weight-bold">Nécessite </span> <span class="font-weight-bold">Nécessite </span>
<span v-for="(ingredient,index) in spell.ingredients" :key="index"> <span v-for="(ingredient,index) in spell.ingredients" :key="index">
@@ -37,8 +41,8 @@
v-b-tooltip.click v-b-tooltip.click
placement="bottom" placement="bottom"
title="Description copiée !" title="Description copiée !"
class="small text-muted font-italic mt-2"> class="small text-muted mt-2">
<span>{{spell.description}}</span> <span style="white-space: pre-wrap;">{{spell.description}}</span>
</div> </div>
<div class="mt-2"> <div class="mt-2">
@@ -53,7 +57,7 @@
<span> = {{variable.description}}</span> <span> = {{variable.description}}</span>
</span> </span>
</div> </div>
<footer class="text-right"> <footer v-if="user" class="text-right">
<a class="h5 text-secondary mr-1"> <a class="h5 text-secondary mr-1">
<i class="mad" @click="editSpell(spell)">edit</i> <i class="mad" @click="editSpell(spell)">edit</i>
</a> </a>
@@ -82,14 +86,19 @@ export default {
} }
}, },
created() { created() {
this.main_school = this.main_school.toLowerCase(); this.main_school = this.main_school.toLowerCase()
},
computed: {
user() {
return this.$store.state.user
}
}, },
methods: { methods: {
editSpell(spell) { editSpell(spell) {
this.$emit('editSpell', spell) this.$emit('editSpell', spell)
}, },
deleteSpell(spell) { deleteSpell(spell) {
Spells.deleteSpell(this.spell.id) Spells.deleteOne(this.spell.id)
.then(() => { .then(() => {
this.$emit('deleteSpell', spell) this.$emit('deleteSpell', spell)
}) })
@@ -122,6 +131,7 @@ export default {
@include colorschool(hydromancie,#3f68c7); @include colorschool(hydromancie,#3f68c7);
@include colorschool(electromancie,#cd9731); @include colorschool(electromancie,#cd9731);
@include colorschool(terramancie,#7e5540); @include colorschool(terramancie,#7e5540);
@include colorschool(sidéromancie,#58697a);
@include colorschool(caelomancie,#a8a8a8); @include colorschool(caelomancie,#a8a8a8);
@include colorschool(légimancie,#5dbabd); @include colorschool(légimancie,#5dbabd);
@include colorschool(illusiomancie,#9f63a1); @include colorschool(illusiomancie,#9f63a1);

View File

@@ -1,14 +1,80 @@
<template> <template>
<div class="spell-list-wrapper"> <div class="spell-list-wrapper">
<button type="button" class="btn font-display font-weight-bold btn-lg btn-outline-dark btn-block shadow-sm mb-4" @click="showAdd">
<div class="mb-4">
<form>
<div class="form-group mb-2">
<input type="text" class="form-control" v-model="search_text" name="search_terms" id="search_terms" placeholder="Rechercher l'arcane">
</div>
<div class="mb-3">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="search_term" 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 class="form-check-input" type="radio" name="search_term" id="search_fields_description" value="search_fields_description" checked>
<label class="form-check-label" for="search_fields_description">Description</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="search_term" id="search_fields_schools" value="search_fields_schools" disabled>
<label class="form-check-label" for="search_fields_schools">École(s)</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="search_term" id="search_fields_ingredients" value="search_fields_ingredients" disabled>
<label class="form-check-label" for="search_fields_ingredients">Ingrédients</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="search_term" id="search_fields_variables" value="search_fields_variables" disabled>
<label class="form-check-label" for="search_fields_variables">Variables</label>
</div>
</div>
</form>
</div>
<button
v-if="user"
@click="showAdd"
type="button"
class="btn font-display font-weight-bold btn-lg btn-outline-dark btn-block shadow-sm mb-4">
<i class="mad">add</i> <i class="mad">add</i>
<span>Ajouter un sort</span> <span>Ajouter un sort</span>
</button> </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
v-if="filteredSpells.length > 0"
class="spell-list-wrapper">
<div
class="row spells-list"
v-masonry
transition-duration="1s"
item-selector=".spell-card">
<spell-card
class="spell-card"
v-masonry-tile
v-for="(spell) in filteredSpells"
:key="spell.id"
:spell="spell"
@editSpell="editSpell"
@deleteSpell="deleteSpell" />
</div>
<edit-spell-card
v-if="Object.keys(current_edit_spell).length > 0"
:spell="current_edit_spell"
@editSpell="editSpell"
@updateSpell="updateSpell"/>
<add-spell-card
v-if="adding_spell"
@cancelAdd="cancelAdd"
@addSpell="addSpell"/>
</div>
<div
v-else
class="loader-wrapper">
<loader/>
</div> </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>
@@ -31,34 +97,93 @@ export default {
'add-spell-card': AddSpellCard, 'add-spell-card': AddSpellCard,
}, },
props: { props: {
school_id: String school_id: String,
}, },
data() { data() {
return { return {
loading: false,
spells: [], spells: [],
loading: false,
current_edit_spell: {}, current_edit_spell: {},
currentIndex: 1,
adding_spell: false, adding_spell: false,
search_text: "",
} }
}, },
created() { computed: {
this.computeSpells() filteredSpells() {
return this.spells
},
user() {
return this.$store.state.user
}
},
beforeMount() {
this.spells = this.filteredSpells
if (!this.school_id) {
this.getInitialSpells()
} else {
this.getInitialSchoolSpells()
}
},
mounted() {
if (!this.school_id) {
this.scroll()
}
}, },
methods: { methods: {
async fetchSpells() { getInitialSpells() {
if (!this.school_id) { Spells.getPage(this.currentIndex)
const { data } = await Spells.getSpells() .then(v => {
return data
} else {
const { data } = await Schools.getSpellsFromSchool(this.school_id)
return data.spells
}
},
async computeSpells() {
this.loading = true this.loading = true
const displaySpells = await this.fetchSpells() let spells = this.filteredSpells
spells.push(v.data)
this.spells = spells[0]
})
.then(() => {
this.currentIndex++
this.loading = false this.loading = false
this.spells = displaySpells })
.catch(err => {
console.log(err)
})
},
getInitialSchoolSpells() {
Schools.getSpellsFromOne(this.school_id)
.then(v => {
this.loading = true
let spells = this.filteredSpells
spells.push(v.data.spells)
this.spells = spells[0]
})
.then(() => {
this.currentIndex++
this.loading = false
})
.catch(err => {
console.log(err)
})
},
scroll() {
window.onscroll = () => {
if (((window.innerHeight + window.scrollY) - document.body.offsetHeight) >= -1) {
Spells.getPage(this.currentIndex)
.then(v => {
this.loading = true
let spells = this.filteredSpells
for (let i = 0; i < v.data.length; i++) {
const element = v.data[i];
spells.push(element)
}
})
.then(() => {
this.currentIndex++
this.loading = false
})
.catch(err => {
console.log(err)
})
}
}
}, },
editSpell(e) { editSpell(e) {
this.current_edit_spell = e; this.current_edit_spell = e;
@@ -69,10 +194,12 @@ export default {
cancelAdd() { cancelAdd() {
this.adding_spell = false this.adding_spell = false
}, },
// Receives events to update the data
addSpell(e) { addSpell(e) {
Spells.getSpell(e.id) Spells.getOne(e.id)
.then(v => { .then(v => {
this.spells.unshift(v.data) let spells = this.filteredSpells
spells.unshift(v.data)
}) })
.then(() => { .then(() => {
this.adding_spell = false this.adding_spell = false
@@ -96,5 +223,8 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.loader-wrapper {
margin-top: 3rem;
text-align: center;
}
</style> </style>

View File

@@ -0,0 +1,7 @@
import Navbar from './components/global/navbar/navbar'
import Loader from './components/global/loader'
export default [
Navbar,
Loader
]

View File

@@ -1,59 +1,93 @@
// Core // Core
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex'
import VueRouter from 'vue-router' import VueRouter from 'vue-router'
import App from './app.vue' import App from './app.vue'
// Auth import Globals from './global-components.js'
Globals.forEach(component => {
Vue.component(component.name, component)
});
// Environment
require('dotenv').config() require('dotenv').config()
// Router // Cookies
import routes from './routes' import VueCookies from 'vue-cookies'
Vue.use(VueRouter) Vue.use(VueCookies)
Vue.$cookies.config('30d','','')
// Jquery // Jquery
import jquery from 'jquery' import jquery from 'jquery'
window.$ = jquery window.$ = jquery
window.jquery = jquery window.jquery = jquery
// Global styles // Styles
import './assets/scss/_global.scss' import './assets/scss/_global.scss'
// Bootstrap
import 'bootstrap/dist/css/bootstrap.css' import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css' import 'bootstrap-vue/dist/bootstrap-vue.css'
import 'bootstrap/dist/js/bootstrap.js' import 'bootstrap/dist/js/bootstrap.js'
// Plugins
import { BootstrapVue } from 'bootstrap-vue' import { BootstrapVue } from 'bootstrap-vue'
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
// Masonry import { VueMasonryPlugin } from 'vue-masonry'
import { VueMasonryPlugin } from 'vue-masonry';
Vue.use(VueMasonryPlugin) Vue.use(VueMasonryPlugin)
// Clipboard plugin
import clipboard from 'v-clipboard' import clipboard from 'v-clipboard'
Vue.use(clipboard) Vue.use(clipboard)
Vue.config.productionTip = false // FUNCTIONS
var filter = function(text, length, clamp){ var filter = function(text, length, clamp){
clamp = clamp || '...'; clamp = clamp || '...';
var node = document.createElement('div'); var node = document.createElement('div');
node.innerHTML = text; node.innerHTML = text;
var content = node.textContent; var content = node.textContent;
return content.length > length ? content.slice(0, length) + clamp : content; return content.length > length ? content.slice(0, length) + clamp : content;
}; }
Vue.filter('truncate', filter); Vue.filter('truncate', filter)
const router = new VueRouter({ // Router
mode: 'history', import router from './routes'
routes, Vue.use(VueRouter)
linkActiveClass: "",
linkExactActiveClass: "active",
});
const app = new Vue({ // Store
render: h => h(App), Vue.use(Vuex)
router let user = Vue.$cookies.get('U_')
const store = new Vuex.Store({
state: user ?
{ status: { loggedIn: true }, user }
: { status: { loggedIn: false }, user: null },
mutations: {
loginSucceed (state, user) {
state.status.loggedIn = true
state.user = user
},
loginFail (state) {
state.status.loggedIn = false
state.user = null
},
logout(state) {
state.status.loggedIn = false;
state.user = null;
},
registerSucceed (state, user) {
state.status.loggedIn = true
state.user = user
},
registerFail (state) {
state.status.loggedIn = false
state.user = null
},
}
}) })
// Mount Vue
const app = new Vue({
render: h => h(App),
router,
store: store
})
app.$mount('#srs') app.$mount('#srs')

View File

@@ -1,37 +1,42 @@
<template> <template>
<div class="container-fluid" id="index-container"> <div class="container-fluid">
<section class="fullpage"> <section class="d-flex justify-content-center align-items-center">
<div class="px-4"> <main class="px-4">
<h1 class="text-dark mb-4 font-display line-height-100 font-weight-900">"C'est magique, ta gueule."</h1> <h1 class="title text-dark mb-4 font-display line-height-100">"C'est magique, ta gueule."</h1>
<div class="lead font-display font-weight-bold"> N'importe quel MJ, une fois dans sa vie</div> <div class="lead font-display font-weight-bold"> N'importe quel MJ, une fois dans sa vie</div>
<hr class="w-50"> <hr class="w-50">
<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 class="lead"><span class="font-display">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> </main>
</section> </section>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'index', name: 'index-page',
} }
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.fullpage { section {
min-height: calc(100vh - 56px); min-height: calc(100vh - 56px);
display: flex; main {
justify-content: center; width: 1000px;
align-items: center; .title {
h1 {
font-size: 5rem; font-size: 5rem;
font-weight: 900;
text-align: center;
}
.lead {
.font-display {
font-weight: 700;
}
}
} }
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.fullpage { .title {
h1 {
font-size: 3.5rem; font-size: 3.5rem;
} }
} }
}
</style> </style>

View File

@@ -0,0 +1,15 @@
<template>
<div class="container-fluid p-4" id="spell-container">
<h1 class="display-3 font-display mb-3">Écoles</h1>
</div>
</template>
<script>
export default {
name: 'schools-page'
}
</script>
<style>
</style>

View File

@@ -14,7 +14,7 @@ import { RepositoryFactory } from "~/api/repositories"
const Schools = RepositoryFactory.get('schools') const Schools = RepositoryFactory.get('schools')
export default { export default {
name: 'single-school', name: 'single-school-page',
components: { components: {
'spell-list': SpellsList, 'spell-list': SpellsList,
}, },
@@ -30,7 +30,7 @@ export default {
}, },
methods: { methods: {
async fetchSchool(id) { async fetchSchool(id) {
const { data } = await Schools.getSchool(id) const { data } = await Schools.getOne(id)
return data return data
}, },
async computeSchool() { async computeSchool() {

View File

@@ -1,48 +0,0 @@
<template>
<div class="container-fluid p-4" id="spell-container">
<h1 class="display-3 font-display mb-3">Sortilèges</h1>
<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>
<script>
import SpellsList from "../components/spells/spells-list"
export default {
name: 'spells',
components: {
'spell-list': SpellsList,
}
}
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,15 @@
<template>
<div class="container-fluid p-4" id="spell-container">
<h1 class="display-3 font-display mb-3">Sortilège</h1>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div class="container-fluid p-4" id="spell-container">
<h1 class="display-3 font-display mb-3">Sortilèges</h1>
<spell-list/>
</div>
</template>
<script>
import SpellsList from "~/components/spells/spells-list"
export default {
name: 'spells-page',
components: {
'spell-list': SpellsList,
}
}
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,92 @@
<template>
<div class="container-fluid">
<section class="d-flex justify-content-center align-items-center">
<main>
<div class="title font-display mb-3">Connexion</div>
<form @submit="logUser">
<div class="form-group">
<label for="email">Addresse mail</label>
<input
v-model="email"
type="email"
id="email"
class="form-control"
placeholder="john.doe@gmail.com"
autocomplete="email">
<small class="form-text text-muted">Votre addresse mail nous servira principalement à vous identifier et à retrouver votre compte si vous oubliez vos informations de connexion.</small>
</div>
<div class="form-group">
<label for="password">Mot de passe</label>
<input
v-model="password"
type="password"
id="password"
class="form-control"
autocomplete="current-password">
</div>
<button type="submit" class="btn btn-primary">Connexion</button>
<router-link :to="'/inscription'" class="btn btn-dark">Inscription</router-link>
<small class="form-text text-muted">Vous avez oublié votre mot de passe ? Cliquez-ici pour le changer !</small>
</form>
</main>
</section>
</div>
</template>
<script>
// API
import { RepositoryFactory } from "~/api/repositories"
const Users = RepositoryFactory.get('users')
export default {
name: 'login-page',
data() {
return {
email: "",
password: "",
submitted: false,
errors: {
login: ""
}
}
},
methods: {
async logUser(e) {
e.preventDefault()
Users.login({
"mail": this.email,
"password": this.password
})
.then(v => {
let user = v.data.user
this.$cookies.set("U_", user, 60 * 60 * 24 * 30)
this.$store.commit('loginSucceed', user)
this.$router.push('/profil')
})
.catch(() => {
this.$store.commit('loginFailed')
})
}
}
}
</script>
<style lang="scss" scoped>
section {
min-height: calc(100vh - 56px);
main {
width: 600px;
.title {
font-size: 5rem;
font-weight: 700;
text-align: center;
}
}
}
@media only screen and (max-width: 600px) {
.title {
font-size: 3.5rem;
}
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<div class="container-fluid p-4" id="spell-container">
<div class="display-3 font-display mb-3">
<span class="username">{{ user.name }}</span>
<span v-if="user.verified" class="verified"><i class="mad">check_circle</i></span>
</div>
</div>
</template>
<script>
export default {
name: 'profile-page',
computed: {
user() {
return this.$store.state.user
}
},
}
</script>
<style lang="scss" scoped>
.title {
font-size: 5rem;
font-weight: 700;
}
@media only screen and (max-width: 600px) {
.title {
font-size: 3.5rem;
}
}
</style>

View File

@@ -0,0 +1,161 @@
<template>
<div class="container-fluid">
<section class="d-flex justify-content-center align-items-center">
<main>
<div class="title font-display mb-3">Inscription</div>
<form @submit="registerUser">
<div class="form-group">
<label for="email">Pseudo</label>
<input
v-model="name"
:class="{
'is-invalid': errors.name,
'is-valid': submitted && !errors.name }"
type="text"
id="username"
class="form-control"
placeholder="John Doe"
autocomplete="username">
<small v-if="!errors.name" class="form-text text-muted">Vous pouvez changer votre pseudo à tout moment.</small>
<small v-if="errors.name" class="text-danger">{{ errors.name }}</small>
</div>
<div class="form-group">
<label for="email">Addresse mail</label>
<input
v-model="email"
:class="{
'is-invalid': errors.email,
'is-valid': submitted && !errors.email }"
type="email"
id="email"
class="form-control"
placeholder="john.doe@gmail.com"
autocomplete="email">
<small v-if="!errors.email" class="form-text text-muted">Votre addresse mail nous servira principalement à vous identifier et à retrouver votre compte si vous oubliez vos informations de connexion.</small>
<small v-if="errors.email" class="text-danger">{{ errors.email }}</small>
</div>
<div class="form-group">
<label for="password">Mot de passe</label>
<input
:class="{
'is-invalid': errors.password,
'is-valid': submitted && !errors.password }"
v-model="password"
type="password"
id="password"
class="form-control"
autocomplete="password">
</div>
<div class="form-group">
<label for="password_check">Confirmer le mot de passe</label>
<input
v-model="password_check"
:class="{
'is-invalid': errors.password_check || errors.password,
'is-valid': submitted && !errors.password_check}"
type="password"
id="password_check"
class="form-control"
autocomplete="password-verification">
<small v-if="!errors.password && !errors.password_check" class="form-text text-muted">Votre mot de passe doit idéalement contenir des symboles ainsi que des lettres et des chiffres.</small>
<small v-if="errors.password" class="text-danger">{{ errors.password }}</small>
<small v-if="errors.password_check" class="text-danger">{{ errors.password_check }}</small>
</div>
<button type="submit" class="btn btn-dark">Inscription</button>
</form>
</main>
</section>
</div>
</template>
<script>
// API
import { RepositoryFactory } from "~/api/repositories"
const Users = RepositoryFactory.get('users')
export default {
name: 'register-page',
data() {
return {
name: "",
email: "",
password: "",
password_check: "",
submitted: false,
errors: {
name: "",
email: "",
password: "",
password_check: "",
},
}
},
methods: {
async registerUser(e) {
e.preventDefault()
// Resets old errors if any
for (const o in this.errors) {
this.errors[o] = ""
}
this.submitted = true
if (!this.name) {
this.errors.name = "Vous devez renseigner un pseudonyme."
}
if (!this.email) {
this.errors.email = "Vous devez renseigner une addresse mail."
}
if (!this.password) {
this.errors.password = "Vous devez renseigner un mot de passe."
}
if ((this.password.localeCompare(this.password_check)) != 0) {
this.errors.password_check = "Les mots de passe ne correspondent pas."
}
// Stops the method if any errors exist
for (const o in this.errors) {
if (this.errors[o]) {
return true
}
}
Users.register({
"name": this.name,
"mail": this.email,
"password": this.password
})
.then(v => {
let user = v.data.user
this.$cookies.set("U_", user, 60 * 60 * 24 * 30)
this.$store.commit('registerSucceed', user)
this.$router.push('/profil')
})
.catch(err => {
let error = err.response.data.error
this.errors.email = error
this.$store.commit('registerFail')
})
}
}
}
</script>
<style lang="scss" scoped>
section {
min-height: calc(100vh - 56px);
main {
width: 600px;
.title {
font-size: 5rem;
font-weight: 700;
text-align: center;
}
}
}
@media only screen and (max-width: 600px) {
.title {
font-size: 3.5rem;
}
}
</style>

View File

@@ -1,22 +1,91 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
// Pages // Pages
import Index from "./pages/index-page"; import Index from "~/pages/index-page"
import Spells from "./pages/spells-page";
import SchoolSingle from "./pages/schools/single-school-page"; import Spells from "~/pages/spells/spells-page"
import SpellSingle from "~/pages/spells/single-spell-page"
import Schools from "./pages/schools/schools-page"
import SchoolSingle from "~/pages/schools/single-school-page"
import Login from "~/pages/user/login-page"
import Register from "~/pages/user/register-page"
import Profile from "~/pages/user/profile-page"
// Routes // Routes
const routes = [ const routes = [
{ {
path: "*", redirect: "/", path: "*",
redirect: "/",
}, },
{ {
path: '/', component: Index, path: '/',
component: Index,
}, },
{ {
path: '/spells', component: Spells, path: '/connexion',
}, component: Login,
{ meta: {
path: '/school/:id', component: SchoolSingle, props: true antiAuthGuard: true
} }
]; },
{
path: '/inscription',
component: Register,
meta: {
antiAuthGuard: true
}
},
{
path: '/sorts',
component: Spells,
},
{
path: '/sorts/:id',
component: SpellSingle,
props: true,
},
{
path: '/ecoles',
component: Schools,
},
{
path: '/ecoles/:id',
component: SchoolSingle,
props: true,
},
{
path: '/profil',
component: Profile,
props: true,
meta: {
authGuard: true,
}
},
]
export default routes; const router = new VueRouter({
mode: 'history',
routes,
linkActiveClass: "",
linkExactActiveClass: "active",
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.authGuard)) {
if (Vue.$cookies.get('U_') == null) {
next({
path: '/connexion',
params: { nextUrl: to.fullPath },
})
} else {
next()
}
} else {
next()
}
})
export default router;

View File

@@ -2,18 +2,46 @@ DROP DATABASE IF EXISTS auracle;
CREATE DATABASE auracle CHARACTER SET utf8 COLLATE utf8_bin; CREATE DATABASE auracle CHARACTER SET utf8 COLLATE utf8_bin;
USE auracle; USE auracle;
/* ==== PRIMARY TABLES ==== */ /* =========== PRIMARY TABLES =========== */
/* PERMISSIONS */
CREATE TABLE IF NOT EXISTS `role` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`)
);
/* USERS */
CREATE TABLE IF NOT EXISTS `user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(36) NOT NULL,
`name` VARCHAR(255) NOT NULL DEFAULT "Disciple",
`mail` VARCHAR(255) NOT NULL,
`avatar` VARCHAR(255),
`gender` VARCHAR(255),
`password` VARCHAR(255) NOT NULL,
`role_id` INT UNSIGNED NOT NULL DEFAULT 1,
`verified` BOOLEAN DEFAULT false,
`banned` BOOLEAN DEFAULT false,
PRIMARY KEY(`id`),
FOREIGN KEY(`role_id`) REFERENCES role(`id`)
);
/* SPELLS */ /* SPELLS */
CREATE TABLE IF NOT EXISTS `spell` ( CREATE TABLE IF NOT EXISTS `spell` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL DEFAULT 'Nom du sort', `name` VARCHAR(255) NOT NULL DEFAULT "Nom du sort",
`description` VARCHAR(1000) NOT NULL DEFAULT "Description du sort", `description` VARCHAR(1000) NOT NULL DEFAULT "Description du sort",
`level` INT UNSIGNED DEFAULT 0, `level` INT UNSIGNED DEFAULT 0,
`charge` INT UNSIGNED DEFAULT 0, `charge` INT UNSIGNED DEFAULT 0,
`cost` VARCHAR(255) DEFAULT "0", `cost` VARCHAR(255) DEFAULT "0",
`is_ritual` BOOLEAN DEFAULT false, `is_ritual` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`) `published` BOOLEAN DEFAULT true,
`public` BOOLEAN DEFAULT true,
`author_id` INT UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
FOREIGN KEY(`author_id`) REFERENCES user(`id`)
); );
/* META SCHOOLS */ /* META SCHOOLS */
@@ -38,7 +66,7 @@ CREATE TABLE IF NOT EXISTS `school` (
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.", `description` VARCHAR(255) NOT NULL DEFAULT "Une langue de salamandre de feu encore chaude.",
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
@@ -49,17 +77,6 @@ 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 */
@@ -91,3 +108,69 @@ CREATE TABLE IF NOT EXISTS `spell_ingredient` (
FOREIGN KEY(`spell_id`) REFERENCES spell(`id`), FOREIGN KEY(`spell_id`) REFERENCES spell(`id`),
FOREIGN KEY(`ingredient_id`) REFERENCES ingredient(`id`) FOREIGN KEY(`ingredient_id`) REFERENCES ingredient(`id`)
); );
/* Ajout d'une nouvelle ligne avant l'insert de description */
DELIMITER $$
CREATE TRIGGER `multiLine` BEFORE INSERT ON `spell` FOR EACH ROW
BEGIN
SET NEW.description = replace(NEW.description, '<l>', '\n');
END$$
DELIMITER ;
/* =========== PRIMARY INSERTS =========== */
SET NAMES utf8;
USE auracle;
-- PERMISSIONS
INSERT INTO `role` (name, description) VALUES
("Visiteur", "Utilisateur normal, peut consulter les sorts."),
("Scribe", "Gardiens des écrits, les scribes sont capables de modifier et d'ajouter des sortilèges."),
("Arcanologue", "Maîtres de l'arcane, ils ont le pouvoir et la responsabilité de juger les sortilèges récents et de les supprimer, ou valider."),
("Augure", "Régents des grimoires, ils ont droit d'accès à l'intégralité des informations connues, et pouvoir absolu sur le savoir arcanique.");
-- META SCHOOLS
INSERT INTO `meta_school` (name, description) VALUES
('Magies blanches', 'Magies disciplinant les arts de soins et de lumières.'),
('Magies noires', 'Magies disciplinant l\'art de la mort et des secrets.'),
('Magies élémentaires', 'Magies disciplinant les éléments basiques tels que l\'eau, la foudre et le feu, pour n\'en citer que les plus populaires.'),
('Magies spirituelles', 'Magies disciplinant l\'esprit, tant pour le défendre que l\'attaquer.'),
('Magies spatio-temporelles', 'Magies régissant le temps et l\'espace.'),
('Magies affiliées', 'Magies rattachées à une forme d\'énergie magique particulière.'),
('Magies autres', 'Magies trop spécifiques et ne rentrant dans aucune autre grande école.');
-- SCHOOLS
INSERT INTO `school` (name, description, meta_school_id) VALUES
('Lumomancie', 'Discipline arcanique de la lumière.', 1),
('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 1),
('Obstrumancie', 'Discipline arcanique de la protection et des sceaux.', 1),
('Tenebromancie', 'Discipline arcanique de la lumière.', 2),
('Necromancie', 'Discipline arcanique de la mort.', 2),
('Morbomancie', 'Discipline arcanique des maladies et malédictions.', 2),
('Pyromancie', 'Discipline arcanique du feu.', 3),
('Hydromancie', 'Discipline arcanique de l\'eau.', 3),
('Electromancie', 'Discipline arcanique de la foudre.', 3),
('Terramancie', 'Discipline arcanique de la terre.', 3),
('Sidéromancie', 'Discipline arcanique des métaux rares et précieux.', 3),
('Caelomancie', 'Discipline arcanique de l\'air.', 3),
('Légimancie', 'Discipline arcanique de la lecture et du contrôle spirituel.', 4),
('Illusiomancie', 'Discipline arcanique des illusions.', 4),
('Cruciomancie', 'Discipline arcanique de la destruction spirituelle.', 4),
('Chronomancie', 'Discipline arcanique du temps.', 5),
('Spatiomancie', 'Discipline arcanique de l\'espace.', 5),
('Kénomancie', 'Discipline arcanique du néant.', 6),
('Lutomancie', 'Discipline arcanique des abysses.', 6),
('Échomancie', 'Discipline arcanique de la résolution animique.', 6),
('Protomancie', 'Discipline arcanique de la magie pure.', 7),
('Rebumancie', 'Discipline arcanique de la lumière.', 7),
('Vocamancie', 'Discipline arcanique de la lumière.', 7),
('Somamancie', 'Discipline arcanique de la maîtrise corporelle.', 7),
('Antimancie', 'Discipline arcanique de l\'annulation arcanique.', 7);
-- INGREDIENTS
INSERT INTO `ingredient` (name, description) VALUES
('Volonté', 'La force de volonté du lanceur, concentrée sur un objectif'),
('Geste', 'Un geste précis facilitant la canalisation magique');
-- VARIABLES
INSERT INTO `variable` (description) VALUES
('Nombre de personnes soignées');

View File

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

View File

@@ -0,0 +1,37 @@
USE auracle;
/* Insertions de masses */
DELIMITER $$
CREATE PROCEDURE insertIntoSchoolRange(IN delimiter_start INT, IN delimiter_end INT, IN id_school INT)
BEGIN
SET @i = delimiter_start;
WHILE @i <= delimiter_end DO
INSERT INTO spell_school (spell_id, school_id) VALUES (@i, id_school);
SET @i = @i + 1;
END WHILE;
END$$
DELIMITER ;
CALL insertIntoSchoolRange(1, 25, 1);
CALL insertIntoSchoolRange(26, 50, 2);
CALL insertIntoSchoolRange(51, 70, 3);
CALL insertIntoSchoolRange(71, 95, 4);
CALL insertIntoSchoolRange(96, 111, 5);
CALL insertIntoSchoolRange(112, 115, 6);
CALL insertIntoSchoolRange(116, 141, 7);
CALL insertIntoSchoolRange(142, 167, 8);
CALL insertIntoSchoolRange(168, 193, 9);
CALL insertIntoSchoolRange(194, 217, 10);
CALL insertIntoSchoolRange(218, 220, 11);
CALL insertIntoSchoolRange(221, 242, 12);
CALL insertIntoSchoolRange(243, 257, 13);
CALL insertIntoSchoolRange(258, 272, 14);
CALL insertIntoSchoolRange(273, 283, 15);
CALL insertIntoSchoolRange(284, 301, 16);
CALL insertIntoSchoolRange(302, 322, 17);
CALL insertIntoSchoolRange(323, 339, 19);
CALL insertIntoSchoolRange(340, 341, 20);
CALL insertIntoSchoolRange(342, 356, 21);
CALL insertIntoSchoolRange(357, 387, 22);
CALL insertIntoSchoolRange(388, 396, 24);
CALL insertIntoSchoolRange(397, 403, 25);

48
functions.js Normal file
View File

@@ -0,0 +1,48 @@
// Error handling
const { HttpError } = require('./validations/Errors')
const regexInt = RegExp(/^[1-9]\d*$/)
const regexXSS = RegExp(/<[^>]*script/)
// Check if int for param validation
const paramIntCheck = (req, res, next, input) => {
try {
if (regexInt.test(input)) {
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
})
)
}
}
// Check if script injection attempt
const isXSSAttempt = (string) => {
if (regexXSS.test(string)) {
return true
} else {
return false
}
}
// Check if object is null
const isEmptyObject = (obj) => {
if (Object.keys(obj).length === 0 && obj.constructor === Object) {
return true
} else {
return false
}
}
module.exports = {
paramIntCheck,
isXSSAttempt,
isEmptyObject
}

View File

@@ -3,6 +3,7 @@ const bookshelf = require('../database/bookshelf').bookshelf
let User = bookshelf.Model.extend({ let User = bookshelf.Model.extend({
tableName: 'user', tableName: 'user',
hidden: ['id', 'password']
}) })
module.exports = bookshelf.model('User', User) module.exports = bookshelf.model('User', User)

11
package-lock.json generated
View File

@@ -1287,6 +1287,11 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"uuid": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
} }
} }
}, },
@@ -2376,9 +2381,9 @@
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
}, },
"uuid": { "uuid": {
"version": "7.0.3", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz",
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q=="
}, },
"v8flags": { "v8flags": {
"version": "3.1.3", "version": "3.1.3",

View File

@@ -4,7 +4,8 @@
"description": "API for Auracle database", "description": "API for Auracle database",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "start" "api": "node index.js",
"client": "cd client && npm run serve"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -37,6 +38,7 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"knex": "^0.21.1", "knex": "^0.21.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1" "mysql": "^2.18.1",
"uuid": "^8.2.0"
} }
} }

View File

@@ -10,7 +10,8 @@ const IngredientValidation = require("../validations/IngredientValidation")
v.addSchema(IngredientValidation, "/IngredientValidation") v.addSchema(IngredientValidation, "/IngredientValidation")
// Validations // Validations
const regexXSS = RegExp(/<[^>]*script/) const isXSSAttempt = require('../functions').isXSSAttempt
const isEmptyObject = require('../functions').isEmptyObject
// Error handling // Error handling
const { HttpError } = require('../validations/Errors') const { HttpError } = require('../validations/Errors')
@@ -68,11 +69,11 @@ class IngredientRepository {
addOne(igr) { addOne(igr) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(igr)) { if (isEmptyObject(igr)) {
reject(new HttpError(403, "Error: Ingredient cannot be nothing !")) reject(new HttpError(403, "Error: Ingredient cannot be nothing !"))
} else if (!v.validate(igr, IngredientValidation).valid) { } else if (!v.validate(igr, IngredientValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors)) reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors))
} else if (this.isXSSAttempt(igr.description)) { } else if (isXSSAttempt(igr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -103,11 +104,11 @@ class IngredientRepository {
updateOne(id, igr) { updateOne(id, igr) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(igr)) { if (isEmptyObject(igr)) {
reject(new HttpError(403, "Error: Ingredient cannot be nothing !")) reject(new HttpError(403, "Error: Ingredient cannot be nothing !"))
} else if (!v.validate(igr, IngredientValidation).valid) { } else if (!v.validate(igr, IngredientValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors)) reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors))
} else if (this.isXSSAttempt(igr.description)) { } else if (isXSSAttempt(igr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
model.forge({id: id}) model.forge({id: id})
@@ -165,24 +166,6 @@ class IngredientRepository {
}) })
}) })
} }
// 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 module.exports = IngredientRepository

View File

@@ -10,7 +10,8 @@ const SchoolValidation = require("../validations/SchoolValidation")
v.addSchema(SchoolValidation, "/SchoolValidation") v.addSchema(SchoolValidation, "/SchoolValidation")
// Validations // Validations
const regexXSS = RegExp(/<[^>]*script/) const isXSSAttempt = require('../functions').isXSSAttempt
const isEmptyObject = require('../functions').isEmptyObject
// Error handling // Error handling
const { HttpError } = require('../validations/Errors') const { HttpError } = require('../validations/Errors')
@@ -59,7 +60,7 @@ class SchoolRepository {
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err)
reject(new HttpError(500, "Couldn't get school")) reject(new HttpError(500, "Couldn't get spells from school"))
}) })
}) })
} }
@@ -67,11 +68,11 @@ class SchoolRepository {
addOne(s) { addOne(s) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: School cannot be nothing !")) reject(new HttpError(403, "Error: School cannot be nothing !"))
} else if (!v.validate(s, SchoolValidation).valid) { } else if (!v.validate(s, SchoolValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors)) reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors))
} else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -103,11 +104,11 @@ class SchoolRepository {
updateOne(id, s) { updateOne(id, s) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: School cannot be nothing !")) reject(new HttpError(403, "Error: School cannot be nothing !"))
} else if (!v.validate(s, SchoolValidation).valid) { } else if (!v.validate(s, SchoolValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors)) reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors))
} else if (this.isXSSAttempt(s.name) || this.isXSSAttempt(s.description)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
model.forge({id: id}) model.forge({id: id})
@@ -166,24 +167,6 @@ class SchoolRepository {
}) })
}) })
} }
// 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 module.exports = SchoolRepository

View File

@@ -10,7 +10,8 @@ const SpellValidation = require("../validations/SpellValidation")
v.addSchema(SpellValidation, "/SpellValidation") v.addSchema(SpellValidation, "/SpellValidation")
// Validations // Validations
const regexXSS = RegExp(/<[^>]*script/) const isXSSAttempt = require('../functions').isXSSAttempt
const isEmptyObject = require('../functions').isEmptyObject
// Error handling // Error handling
const { HttpError } = require('../validations/Errors') const { HttpError } = require('../validations/Errors')
@@ -34,6 +35,40 @@ class SpellRepository {
}) })
} }
getAllPublic() {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'public' : 1 })
.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 public spells"))
})
})
}
getPage(page) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'public' : 1 })
.fetchPage({
pageSize: 20,
page: page,
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 public spells"))
})
})
}
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() model.forge()
@@ -52,11 +87,11 @@ class SpellRepository {
addOne(s) { addOne(s) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !")) reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, SpellValidation).valid) { } else if (!v.validate(s, SpellValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).errors)) 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)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -112,11 +147,11 @@ class SpellRepository {
updateOne(id, s) { updateOne(id, s) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !")) reject(new HttpError(403, "Error: Spell cannot be nothing !"))
} else if (!v.validate(s, SpellValidation).valid) { } else if (!v.validate(s, SpellValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).errors)) 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)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || isXSSAttempt(s.cost)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
model.forge({id: id}) model.forge({id: id})
@@ -222,24 +257,6 @@ class SpellRepository {
}) })
}) })
} }
// 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 module.exports = SpellRepository

View File

@@ -5,6 +5,7 @@ const model = require('../models/user-model')
// Hashing and passwords // Hashing and passwords
const bcrypt = require('bcrypt') const bcrypt = require('bcrypt')
const { v4: uuidv4 } = require('uuid')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
@@ -13,10 +14,8 @@ const UserValidation = require("../validations/UserValidation")
v.addSchema(UserValidation, "/UserValidation") v.addSchema(UserValidation, "/UserValidation")
// Validations // Validations
const regexXSS = RegExp(/<[^>]*script/) const isXSSAttempt = require('../functions').isXSSAttempt
const isEmptyObject = require('../functions').isEmptyObject
// Error handling
const { HttpError } = require('../validations/Errors')
class UserRepository { class UserRepository {
@@ -25,29 +24,154 @@ class UserRepository {
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._model.forge() model.forge()
.fetchAll() .fetchAll()
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
.catch(err => { .catch(() => {
console.log(err) reject({
reject(new HttpError(500, "Couldn't get users")) "message": "Database error, couldn't get all users.",
"code": 500
})
}) })
}) })
} }
getOne(id) { getOneByUUID(uuid, full) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._model.forge() model.forge()
.where({ 'id' : id }) .where({ 'uuid' : uuid })
.fetch() .fetch()
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true, visibility: !full }))
}) })
.catch(err => { .catch(err => {
console.log(err) reject({
reject(new HttpError(500, "Couldn't get user")) "message": "User with this UUID was not found.",
"code": 404
})
})
})
}
getOneByEmail(mail, full) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'mail': mail })
.fetch()
.then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full }))
})
.catch(() => {
reject({
"message": "User with this email was not found.",
"code": 404
})
})
})
}
addOne(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({
"message": "Request body cannot be empty.",
"code": 403
})
} else if (!v.validate(u, UserValidation).valid) {
reject({
"message": "Schema is not valid - " + v.validate(u, UserValidation).errors,
"code": 403
})
} else if (isXSSAttempt(u.name) || isXSSAttempt(u.password) || isXSSAttempt(u.mail)) {
reject({
"message": "Injection attempt detected, aborting the request.",
"code": 403
})
} else {
let hash = await bcrypt.hash(u.password, 10)
let uuid = uuidv4()
this.checkIfEmailAvailable(u.mail)
.then(() => {
bookshelf.transaction(t => {
return model.forge({
'uuid': uuid,
'name': u.name,
'mail': u.mail,
'password': hash,
})
.save(null, {
transacting: t
})
.catch(err => {
throw err
})
})
.then(() => {
return this.getOneByUUID(uuid, false)
})
.then(newUser => {
resolve({
"message": "Account successfully created !",
"user": newUser,
})
})
.catch(err => {
resolve({
"message": "An error has occured while creating your account.",
"code": 500,
})
})
})
.catch(err => {
reject(err)
})
}
})
}
// Log user with an email address and a password
logUser(mail, password) {
return new Promise((resolve, reject) => {
this.getOneByEmail(mail, true)
.then(async fetchedUser => {
let match = await bcrypt.compare(password, fetchedUser.password)
delete fetchedUser.id
delete fetchedUser.password
if (match) {
resolve({
"message": "User successfully logged in !",
"user": fetchedUser,
})
} else {
reject({
"message": "Les informations de connexion sont erronées.",
})
}
})
.catch(err => {
reject(err)
})
})
}
// Check if one user already has that email
checkIfEmailAvailable(mail) {
return new Promise((resolve, reject) => {
this.getOneByEmail(mail, false)
.then(() => {
reject({
"message": "L'email est déjà utilisé par un autre utilisateur.",
"code": 403
})
})
.catch(() => {
resolve(true)
}) })
}) })
} }

View File

@@ -10,7 +10,8 @@ const VariableValidation = require("../validations/VariableValidation")
v.addSchema(VariableValidation, "/VariableValidation") v.addSchema(VariableValidation, "/VariableValidation")
// Validations // Validations
const regexXSS = RegExp(/<[^>]*script/) const isXSSAttempt = require('../functions').isXSSAttempt
const isEmptyObject = require('../functions').isEmptyObject
// Error handling // Error handling
const { HttpError } = require('../validations/Errors') const { HttpError } = require('../validations/Errors')
@@ -67,11 +68,11 @@ class VariableRepository {
addOne(vr) { addOne(vr) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(vr)) { if (isEmptyObject(vr)) {
reject(new HttpError(403, "Error: Variable cannot be nothing !")) reject(new HttpError(403, "Error: Variable cannot be nothing !"))
} else if (!v.validate(vr, VariableValidation).valid) { } else if (!v.validate(vr, VariableValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors)) reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors))
} else if (this.isXSSAttempt(vr.description)) { } else if (isXSSAttempt(vr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -101,11 +102,11 @@ class VariableRepository {
updateOne(id, vr) { updateOne(id, vr) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Checks if body exists and if the model fits, and throws errors if it doesn't // Checks if body exists and if the model fits, and throws errors if it doesn't
if (this.isEmptyObject(vr)) { if (isEmptyObject(vr)) {
reject(new HttpError(403, "Error: Variable cannot be nothing !")) reject(new HttpError(403, "Error: Variable cannot be nothing !"))
} else if (!v.validate(vr, VariableValidation).valid) { } else if (!v.validate(vr, VariableValidation).valid) {
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors)) reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors))
} else if (this.isXSSAttempt(vr.description)) { } else if (isXSSAttempt(vr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject(new HttpError(403, 'Injection attempt detected, aborting the request.'))
} else { } else {
model.forge({id: id}) model.forge({id: id})
@@ -162,24 +163,6 @@ class VariableRepository {
}) })
}) })
} }
// 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 module.exports = VariableRepository

View File

@@ -6,17 +6,12 @@ let router = express.Router()
// Connection // Connection
const connection = require('../database/bookshelf') const connection = require('../database/bookshelf')
const db = connection.db const functions = require('../functions')
// Repository // Repository
const IngredientRepository = require('../repositories/ingredient-repository'); const IngredientRepository = require('../repositories/ingredient-repository');
const Ingredients = new IngredientRepository(); const Ingredients = new IngredientRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getIngredients = () => { const getIngredients = () => {
@@ -167,24 +162,7 @@ router.delete('/:id/', async (req, res) => {
}) })
}) })
// Param validation for ID // Param validations
// (check if id is int) (could be refactored) router.param('id', functions.paramIntCheck)
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

@@ -6,17 +6,12 @@ let router = express.Router()
// Connection // Connection
const connection = require('../database/bookshelf') const connection = require('../database/bookshelf')
const db = connection.db const functions = require('../functions')
// Repository // Repository
const MetaSchoolRepository = require('../repositories/meta-school-repository'); const MetaSchoolRepository = require('../repositories/meta-school-repository');
const MetaSchools = new MetaSchoolRepository(); const MetaSchools = new MetaSchoolRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getMetaSchools = () => { const getMetaSchools = () => {
@@ -67,25 +62,7 @@ router.get('/:id/', async (req, res) => {
}) })
}) })
// Param validations
// Param validation for ID router.param('id', functions.paramIntCheck)
// (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

@@ -6,17 +6,12 @@ let router = express.Router()
// Connection // Connection
const connection = require('../database/bookshelf') const connection = require('../database/bookshelf')
const db = connection.db const functions = require('../functions')
// Repository // Repository
const SchoolRepository = require('../repositories/school-repository'); const SchoolRepository = require('../repositories/school-repository');
const Schools = new SchoolRepository(); const Schools = new SchoolRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getSchools = () => { const getSchools = () => {
@@ -166,24 +161,7 @@ router.delete('/:id/', async (req, res) => {
}) })
}) })
// Param validation for ID // Param validations
// (check if id is int) (could be refactored) router.param('id', functions.paramIntCheck)
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

@@ -6,18 +6,37 @@ let router = express.Router()
// Connection // Connection
const connection = require('../database/bookshelf') const connection = require('../database/bookshelf')
const db = connection.db const functions = require('../functions')
// Repository // Repository
const SpellReposity = require('../repositories/spell-repository'); const SpellReposity = require('../repositories/spell-repository');
const Spells = new SpellReposity(); const Spells = new SpellReposity();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL PUBLIC ------------------
const getPublicSpells = () => {
return Spells.getAllPublic()
.catch(err => {
console.log(err)
throw err
})
}
router.get('/', async (req, res) => {
getPublicSpells()
.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 ALL ------------------ // GET ALL ------------------
const getSpells = () => { const getSpells = () => {
return Spells.getAll() return Spells.getAll()
@@ -26,7 +45,7 @@ const getSpells = () => {
throw err throw err
}) })
} }
router.get('/', async (req, res) => { router.get('/private', async (req, res) => {
getSpells() getSpells()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8')
@@ -42,6 +61,29 @@ router.get('/', async (req, res) => {
}) })
}) })
// GET SOME ------------------
const getSomeSpells = (page) => {
return Spells.getPage(page)
.catch(err => {
console.log(err)
throw err
})
}
router.get('/page/:page', async (req, res) => {
getSomeSpells(req.params.page)
.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 ------------------ // GET ONE ------------------
const getSpell = (id) => { const getSpell = (id) => {
@@ -142,25 +184,8 @@ router.delete('/:id/', async (req, res) => {
}) })
}) })
// Param validations
// Param validation for ID router.param('id', functions.paramIntCheck)
// (check if id is int) (could be refactored) router.param('page', functions.paramIntCheck)
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

@@ -12,11 +12,6 @@ const db = connection.db
const UserRepository = require('../repositories/user-repository'); const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository(); const Users = new UserRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getUsers = () => { const getUsers = () => {
@@ -43,16 +38,16 @@ router.get('/', async (req, res) => {
}) })
// GET ONE ------------------ // GET ONE FORM UUID ------------------
const getUser = (id) => { const getUserByUUID = (uuid) => {
return Users.getOne(id) return Users.getOneByUUID(uuid)
.catch(err => { .catch(err => {
console.log(err) console.log(err)
throw err throw err
}) })
} }
router.get('/:id/', async (req, res) => { router.get('/:uuid/', async (req, res) => {
getUser(req.params.id) getUserByUUID(req.params.uuid)
.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))
@@ -68,35 +63,34 @@ router.get('/:id/', async (req, res) => {
}) })
// CREATE ONE ------------------ // LOG A USER ------------------
const addUser = (u) => { const logUser = (mail, password) => {
return new Promise(async (resolve, reject) => { return Users.logUser(mail, password)
.catch(err => {
// Checks if body exists and if the model fits, and throws errors if it doesn't console.log(err)
if (isEmptyObject(u)) { throw err
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 => { router.post('/login', async (req, res) => {
logUser(req.body.mail, req.body.password)
.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 Users.addOne(u)
.catch(err => {
throw err throw err
}) })
} }
@@ -116,77 +110,4 @@ router.post('/', async (req, res) => {
}) })
}) })
// 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 module.exports = router

View File

@@ -6,17 +6,12 @@ let router = express.Router()
// Connection // Connection
const connection = require('../database/bookshelf') const connection = require('../database/bookshelf')
const db = connection.db const functions = require('../functions')
// Repository // Repository
const VariableRepository = require('../repositories/variable-repository'); const VariableRepository = require('../repositories/variable-repository');
const Variables = new VariableRepository(); const Variables = new VariableRepository();
const regexInt = RegExp(/^[1-9]\d*$/)
// Error handling
const { HttpError } = require('../validations/Errors')
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getvariables = () => { const getvariables = () => {
@@ -167,24 +162,7 @@ router.delete('/:id/', async (req, res) => {
}) })
}) })
// Param validation for ID // Param validations
// (check if id is int) (could be refactored) router.param('id', functions.paramIntCheck)
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,7 +5,6 @@ const User = {
"name": { "type": "string" }, "name": { "type": "string" },
"mail": { "type": "string" }, "mail": { "type": "string" },
"password": { "type": "string" }, "password": { "type": "string" },
"banned": { "type": "boolean"},
}, },
"required": ["name", "password", "mail"] "required": ["name", "password", "mail"]
} }