69 Commits

Author SHA1 Message Date
Alexis
da6cd44987 Added ESlint to client and applied rules 2021-01-21 11:16:09 +01:00
Alexis
4b25f63531 Added dist to gitignore 2021-01-21 11:15:13 +01:00
Alexis
b318a88023 Implemented ESlint and passed down the rules 2021-01-20 19:07:56 +01:00
Joururi
615cced6ed Merge pull request #42 from AlexisNP/issues/api-auth
Authentication system has been validated
2021-01-19 23:03:58 +01:00
Alexis
b5e6f73d01 Authguard sub routes 2021-01-19 22:59:55 +01:00
Alexis
efb9195053 Converted data inserts to csv 2021-01-19 22:42:59 +01:00
Alexis
e2061d6595 Updated module tree 2021-01-19 21:27:06 +01:00
Alexis
e8ca2416b7 Added authGuard function and its implementations
Also refactored small code mistakes.
2021-01-19 18:52:52 +01:00
Alexis
410a58fc09 Updated user model and repo 2021-01-18 22:06:36 +01:00
Alexis
413d458a80 Added auth routes entry point 2021-01-18 22:06:19 +01:00
Alexis
bb721e6ab4 Removed JWT 2021-01-18 22:05:01 +01:00
Alexis
39ee29c65a Added bookshelf model for api_tokens 2021-01-18 22:04:16 +01:00
Alexis
b7cc851cd6 Added db table for api_tokens 2021-01-18 22:04:01 +01:00
Alexis
d3c5e0a20c Fixed typo in user repo 2021-01-16 23:39:13 +01:00
Alexis
7170eb0dee Changed root alias from ~ to @ in the client 2021-01-16 19:56:54 +01:00
Alexis
50ba8b7f9c Added prewrap class for api text 2021-01-16 19:56:11 +01:00
Alexis
e9640ec2d2 Added permission description for user API 2021-01-16 19:55:50 +01:00
Joururi
2b3b890228 Merge pull request #39 from AlexisNP/issues/permissions 2021-01-02 19:57:44 +01:00
Alexis
16b7b81166 Updated role model and created perm model in API 2021-01-02 12:32:24 +01:00
Alexis
5cf2c1daa7 Added db permissions models 2021-01-02 11:40:05 +01:00
Joururi
2959d777b7 Merge pull request #38 from AlexisNP/issues/user_spells
Added getSpellsFromUser() method
2021-01-01 22:13:06 +01:00
Alexis
b313393af6 Added getSpellsFromUser() method 2021-01-01 22:07:22 +01:00
Alexis
1759f71876 Removed bat insertion 2020-12-30 22:26:59 +01:00
Alexis
dde2e63b61 Temporary test ground for updating user form 2020-12-30 22:17:28 +01:00
Alexis
41575f40db Fixed template signup mail 2020-12-30 22:13:39 +01:00
Alexis
70283da8e0 Added favicon.png for public client files 2020-12-30 22:13:25 +01:00
Joururi
0a77206bcb Merge pull request #34 from AlexisNP/issues/roles
Added roles to API
2020-12-30 22:04:05 +01:00
Alexis
28c869070a User repository methods updated with roles 2020-12-30 21:53:27 +01:00
Alexis
3bdf43d11b Updated models to include roles 2020-12-30 21:52:06 +01:00
Alexis
0fd0253fb4 Updated database environment 2020-12-30 21:51:41 +01:00
Alexis
23ab785163 Refactored global api checks for params 2020-12-28 12:25:29 +01:00
Alexis
f7a09c02ae - Removed HttpError and refactored old code using it 2020-12-28 12:23:54 +01:00
Joururi
5f7e5499f7 Merge pull request #31 from AlexisNP/evol/verification
User verification system
2020-12-27 19:13:19 +01:00
Alexis
3c2326ef92 Fixed a link for email template 2020-12-27 19:09:09 +01:00
Alexis
752cfb4227 Reinstated valid register form 2020-12-27 19:09:00 +01:00
Alexis
1b11db4890 Added verif route 2020-12-27 19:08:18 +01:00
Alexis
32c17407e2 Added user verif method and refactored some indents 2020-12-27 19:08:09 +01:00
Alexis
da73fa5241 Removed temp authguard measure, will need to refactor it anyays 2020-12-27 19:06:46 +01:00
Alexis
d64e447b67 Sent an email during registeration process 2020-12-27 18:14:26 +01:00
Alexis
dba451c588 Removed test mail sending 2020-12-27 18:14:10 +01:00
Alexis
39521f063a Added db field for verif token 2020-12-27 18:13:56 +01:00
Alexis
1ea21df4d9 Added registration mail template 2020-12-27 17:28:58 +01:00
Alexis
10fdbade66 Added registration mail sending method 2020-12-27 17:28:49 +01:00
Alexis
bdfdb1d21e Added handlebars and nodemailer modules 2020-12-27 17:28:17 +01:00
AlexisNP
dba0521ad9 Merge pull request #29 from AlexisNP/issues/verifban
API user verifications
2020-12-26 23:07:02 +01:00
Alexis
c9bd0aa0ab Changed login and register views accordingly 2020-12-26 22:55:19 +01:00
Alexis
15ba9e19c9 Added API check for banned/unverified users 2020-12-26 22:54:26 +01:00
AlexisNP
d311042b51 Merge pull request #28 from AlexisNP/issues/authguard
Authguard for router
2020-12-26 21:07:05 +01:00
Alexis
3cf131ef6f Reinstated old code (works with new method) 2020-12-26 20:59:41 +01:00
AlexisNP
1ac0968e57 Merge pull request #25 from AlexisNP/issues/cookies
Added cookie for login
2020-12-26 17:13:15 +01:00
Alexis
36a82a643e Added comments explaining the changes 2020-12-26 17:07:09 +01:00
Alexis
79c34a6ad1 Added cookie check and changed store actions 2020-12-26 17:06:52 +01:00
Alexis
167722fab6 Removed global cookie expiration 2020-12-26 17:06:25 +01:00
Alexis
a95a617e8b Cleanup API crew 2020-12-26 17:01:00 +01:00
AlexisNP
b8e12c1665 Merge pull request #24 from AlexisNP/issues/single_pages
- Added detail pages for both spells and schools
2020-12-24 22:56:11 +01:00
Alexis
7b932f1b86 - Added detail pages for both spells and schools 2020-12-24 22:54:18 +01:00
AlexisNP
d69ea0fa72 Merge pull request #21 from AlexisNP/issues/meta
- Render meta elements
2020-12-24 18:29:33 +01:00
Alexis
02c0216c26 - Added title and meta info to current views 2020-12-24 18:24:31 +01:00
Alexis
ad84673fba - Added vue-meta and fixed vulnerabilities 2020-12-24 18:23:32 +01:00
AlexisNP
1e334b8c05 Merge pull request #19 from AlexisNP/register_date
- Implemented register date for users
2020-12-24 18:09:41 +01:00
Alexis
01df57f510 - Implemented register date for users 2020-12-24 17:55:32 +01:00
Alexis
772b225072 Merge branch 'develop/vue/store' into dev 2020-12-23 20:46:48 +01:00
Alexis
65abb41a9f - Implemented store for register methods 2020-12-23 20:25:43 +01:00
Alexis
fdad47e84e - Fixed an error with 200 codes and translated API errors 2020-12-23 18:10:02 +01:00
Alexis
20f946a945 - Added email availability checker in API 2020-12-19 18:15:39 +01:00
Alexis
eba2669176 - Fixed the way components get store data 2020-12-19 17:04:22 +01:00
Alexis
fa945e45c9 - Fixed my revert hahaha :) 2020-12-18 20:34:47 +01:00
Alexis
0587656ba6 - Store files 2020-12-18 19:34:02 +01:00
Alexis
de90b7d747 Revert "- Added timeline page (will need to be redone anyways)"
This reverts commit 0178f15bf1.
2020-12-18 18:55:05 +01:00
81 changed files with 9267 additions and 5737 deletions

6
.gitignore vendored
View File

@@ -1,6 +1,6 @@
.DS_Store .DS_Store
node_modules node_modules
/dist dist
# local env files # local env files
.env.local .env.local
@@ -22,3 +22,7 @@ yarn-error.log*
# creds # creds
.env .env
# db files
*.csv
*.ods

View File

@@ -4,83 +4,104 @@ USE auracle;
/* =========== PRIMARY TABLES =========== */ /* =========== PRIMARY TABLES =========== */
/* PERMISSIONS */ -- ROLES
CREATE TABLE IF NOT EXISTS `role` ( CREATE TABLE IF NOT EXISTS `role` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL, `name` VARCHAR(255) NOT NULL,
`description` VARCHAR(255) NOT NULL, `description` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`) PRIMARY KEY(`id`)
); );
/* USERS */ -- PERMISSIONS
CREATE TABLE IF NOT EXISTS `permission` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`slug` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`)
);
-- USERS
CREATE TABLE IF NOT EXISTS `user` ( CREATE TABLE IF NOT EXISTS `user` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`uuid` VARCHAR(36) NOT NULL, `uuid` VARCHAR(36) NOT NULL UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Disciple", `name` VARCHAR(255) NOT NULL DEFAULT "Disciple",
`mail` VARCHAR(255) NOT NULL, `mail` VARCHAR(255) NOT NULL,
`avatar` VARCHAR(255), `avatar` VARCHAR(255),
`gender` VARCHAR(255), `gender` VARCHAR(255),
`register_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`password` VARCHAR(255) NOT NULL, `password` VARCHAR(255) NOT NULL,
`role_id` INT UNSIGNED NOT NULL DEFAULT 1, `role_id` INT UNSIGNED NOT NULL DEFAULT 1,
`verified` BOOLEAN DEFAULT false, `verified` BOOLEAN DEFAULT false,
`verification_token` VARCHAR(255),
`banned` BOOLEAN DEFAULT false, `banned` BOOLEAN DEFAULT false,
PRIMARY KEY(`id`), PRIMARY KEY(`id`),
FOREIGN KEY(`role_id`) REFERENCES role(`id`) FOREIGN KEY(`role_id`) REFERENCES role(`id`)
); );
/* SPELLS */ -- API_TOKENS
CREATE TABLE IF NOT EXISTS `api_token` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`value` VARCHAR(255) NOT NULL,
`user_uuid` VARCHAR(36) NOT NULL UNIQUE,
PRIMARY KEY(`id`),
FOREIGN KEY(`user_uuid`) REFERENCES user(`uuid`)
);
-- 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 UNIQUE,
`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,
`published` BOOLEAN DEFAULT true, `published` BOOLEAN DEFAULT false,
`public` BOOLEAN DEFAULT true, `public` BOOLEAN DEFAULT true,
`author_id` INT UNSIGNED NOT NULL DEFAULT 1, `author_id` INT UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
FOREIGN KEY(`author_id`) REFERENCES user(`id`) FOREIGN KEY(`author_id`) REFERENCES user(`id`)
); );
/* META SCHOOLS */ -- META SCHOOLS
CREATE TABLE IF NOT EXISTS `meta_school` ( CREATE TABLE IF NOT EXISTS `meta_school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école mère", `name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école mère",
`description` VARCHAR(255) DEFAULT "Description de l'école mère", `description` VARCHAR(255) DEFAULT "Description de l'école mère",
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
/* SCHOOLS */ -- SCHOOLS
CREATE TABLE IF NOT EXISTS `school` ( CREATE TABLE IF NOT EXISTS `school` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école", `name` VARCHAR(255) NOT NULL DEFAULT "Nom de l'école",
`description` VARCHAR(255) DEFAULT "Description de l'école", `description` VARCHAR(255) DEFAULT "Description de l'école",
`published` BOOLEAN DEFAULT false,
`meta_school_id` INT UNSIGNED NOT NULL, `meta_school_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
FOREIGN KEY(`meta_school_id`) REFERENCES meta_school(`id`) FOREIGN KEY(`meta_school_id`) REFERENCES meta_school(`id`)
); );
/* COMMON INGREDIENTS */ -- COMMON INGREDIENTS
CREATE TABLE IF NOT EXISTS `ingredient` ( CREATE TABLE IF NOT EXISTS `ingredient` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`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 encore chaude.", `description` VARCHAR(255) NOT NULL DEFAULT "Une langue de salamandre de feu encore chaude.",
`published` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
/* COMMON VARIABLES */ -- COMMON VARIABLES
CREATE TABLE IF NOT EXISTS `variable` ( CREATE TABLE IF NOT EXISTS `variable` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `id` INT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE,
`description` VARCHAR(255) NOT NULL DEFAULT "Nombre de créatures affectées", `description` VARCHAR(255) NOT NULL DEFAULT "Nombre de créatures affectées",
`published` BOOLEAN DEFAULT false,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
); );
/* ==== ASSOCIATION TABLES ==== */ /* ==== ASSOCIATION TABLES ==== */
/* SPELLS' SCHOOLS */ -- SPELLS' SCHOOLS
/* One spell can have multiple (up to 3) schools */ -- One spell can have multiple (up to 3) schools
CREATE TABLE IF NOT EXISTS `spell_school` ( CREATE TABLE IF NOT EXISTS `spell_school` (
`spell_id` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`school_id` INT UNSIGNED NOT NULL, `school_id` INT UNSIGNED NOT NULL,
@@ -89,8 +110,8 @@ CREATE TABLE IF NOT EXISTS `spell_school` (
FOREIGN KEY(`school_id`) REFERENCES school(`id`) FOREIGN KEY(`school_id`) REFERENCES school(`id`)
); );
/* SPELLS' VARIABLES */ -- SPELLS' VARIABLES
/* One spell can have multiple (up to 2) variables of cost */ -- One spell can have multiple (up to 2) variables of cost
CREATE TABLE IF NOT EXISTS `spell_variable` ( CREATE TABLE IF NOT EXISTS `spell_variable` (
`spell_id` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`variable_id` INT UNSIGNED NOT NULL, `variable_id` INT UNSIGNED NOT NULL,
@@ -99,8 +120,8 @@ CREATE TABLE IF NOT EXISTS `spell_variable` (
FOREIGN KEY(`variable_id`) REFERENCES variable(`id`) FOREIGN KEY(`variable_id`) REFERENCES variable(`id`)
); );
/* SPELLS' VARIABLES */ -- SPELLS' VARIABLES
/* One spell can have multiple ingredients */ -- One spell can have multiple ingredients
CREATE TABLE IF NOT EXISTS `spell_ingredient` ( CREATE TABLE IF NOT EXISTS `spell_ingredient` (
`spell_id` INT UNSIGNED NOT NULL, `spell_id` INT UNSIGNED NOT NULL,
`ingredient_id` INT UNSIGNED NOT NULL, `ingredient_id` INT UNSIGNED NOT NULL,
@@ -109,68 +130,101 @@ CREATE TABLE IF NOT EXISTS `spell_ingredient` (
FOREIGN KEY(`ingredient_id`) REFERENCES ingredient(`id`) FOREIGN KEY(`ingredient_id`) REFERENCES ingredient(`id`)
); );
/* Ajout d'une nouvelle ligne avant l'insert de description */ -- ROLES' PERMISSIONS
-- One role can have any number of permissions, or none at all
CREATE TABLE IF NOT EXISTS `role_permission` (
`role_id` INT UNSIGNED NOT NULL,
`permission_id` INT UNSIGNED NOT NULL,
PRIMARY KEY (`role_id`, `permission_id`),
FOREIGN KEY(`role_id`) REFERENCES role(`id`),
FOREIGN KEY(`permission_id`) REFERENCES permission(`id`)
);
-- Ajout d`une nouvelle ligne avant l`insert de description
DELIMITER $$ DELIMITER $$
CREATE TRIGGER `multiLine` BEFORE INSERT ON `spell` FOR EACH ROW CREATE TRIGGER `multiLine` BEFORE INSERT ON `spell` FOR EACH ROW
BEGIN BEGIN
SET NEW.description = replace(NEW.description, '<l>', '\n'); SET NEW.description = replace(NEW.description, "<l>", "\n");
END$$ END$$
DELIMITER ; DELIMITER ;
/* =========== PRIMARY INSERTS =========== */ /* =========== PRIMARY INSERTS =========== */
SET NAMES utf8; SET NAMES utf8;
USE auracle;
-- PERMISSIONS -- CSV DATA
INSERT INTO `role` (name, description) VALUES LOAD DATA INFILE 'C:/temp/auracle_data/permission.csv'
("Visiteur", "Utilisateur normal, peut consulter les sorts."), INTO TABLE `permission`
("Scribe", "Gardiens des écrits, les scribes sont capables de modifier et d'ajouter des sortilèges."), FIELDS TERMINATED BY ','
("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."), ENCLOSED BY '"'
("Augure", "Régents des grimoires, ils ont droit d'accès à l'intégralité des informations connues, et pouvoir absolu sur le savoir arcanique."); LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
-- META SCHOOLS LOAD DATA INFILE 'C:/temp/auracle_data/role.csv'
INSERT INTO `meta_school` (name, description) VALUES INTO TABLE `role`
('Magies blanches', 'Magies disciplinant les arts de soins et de lumières.'), FIELDS TERMINATED BY ','
('Magies noires', 'Magies disciplinant l\'art de la mort et des secrets.'), ENCLOSED BY '"'
('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.'), LINES TERMINATED BY '\n'
('Magies spirituelles', 'Magies disciplinant l\'esprit, tant pour le défendre que l\'attaquer.'), IGNORE 1 ROWS;
('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 LOAD DATA INFILE 'C:/temp/auracle_data/role_permission.csv'
INSERT INTO `school` (name, description, meta_school_id) VALUES INTO TABLE `role_permission`
('Lumomancie', 'Discipline arcanique de la lumière.', 1), FIELDS TERMINATED BY ','
('Vitamancie', 'Discipline arcanique de la guérison et de l\'énergie vitale.', 1), ENCLOSED BY '"'
('Obstrumancie', 'Discipline arcanique de la protection et des sceaux.', 1), LINES TERMINATED BY '\n'
('Tenebromancie', 'Discipline arcanique de la lumière.', 2), IGNORE 1 ROWS;
('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 LOAD DATA INFILE 'C:/temp/auracle_data/user.csv'
INSERT INTO `ingredient` (name, description) VALUES INTO TABLE `user`
('Volonté', 'La force de volonté du lanceur, concentrée sur un objectif'), FIELDS TERMINATED BY ','
('Geste', 'Un geste précis facilitant la canalisation magique'); ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
-- VARIABLES LOAD DATA INFILE 'C:/temp/auracle_data/api_token.csv'
INSERT INTO `variable` (description) VALUES INTO TABLE `api_token`
('Nombre de personnes soignées'); FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/spell.csv'
INTO TABLE `spell`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/meta_school.csv'
INTO TABLE `meta_school`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/school.csv'
INTO TABLE `school`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/ingredient.csv'
INTO TABLE `ingredient`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/variable.csv'
INTO TABLE `variable`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
LOAD DATA INFILE 'C:/temp/auracle_data/spell_school.csv'
INTO TABLE `spell_school`
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;

View File

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

View File

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

View File

@@ -1,48 +1,35 @@
// Error handling const regexInt = RegExp(/^[1-9]\d*$/);
const { HttpError } = require('./validations/Errors') const regexXSS = RegExp(/<[^>]*script/);
const regexInt = RegExp(/^[1-9]\d*$/)
const regexXSS = RegExp(/<[^>]*script/)
// Check if int for param validation // Check if int for param validation
const paramIntCheck = (req, res, next, input) => { const paramIntCheck = (req, res, next, input) => {
try { try {
if (regexInt.test(input)) { if (regexInt.test(input)) {
next() next();
} else { } else {
throw new Error throw new Error;
} }
} catch (err) { } catch (err) {
err = new HttpError(403, 'Provided ID must be an integer and not zero') res.status(err.code).send(JSON.stringify({
res.status(err.code).send(JSON.stringify( "message": "Le paramètre doit être un entier non-nul.",
{ "code": 403,
"error": err.message,
"code": err.code
}) })
) );
}
} }
};
// Check if script injection attempt // Check if script injection attempt
const isXSSAttempt = (string) => { const isXSSAttempt = (string) => {
if (regexXSS.test(string)) { return regexXSS.test(string);
return true };
} else {
return false
}
}
// Check if object is null // Check if object is null
const isEmptyObject = (obj) => { const isEmptyObject = (obj) => {
if (Object.keys(obj).length === 0 && obj.constructor === Object) { return (Object.keys(obj).length === 0 && obj.constructor === Object);
return true };
} else {
return false
}
}
module.exports = { module.exports = {
paramIntCheck, paramIntCheck,
isXSSAttempt, isXSSAttempt,
isEmptyObject isEmptyObject
} };

View File

@@ -1,60 +1,41 @@
'use strict'; 'use strict';
// MODULES // MODULES
const express = require('express') const express = require('express');
const bodyParser = require('body-parser') const bodyParser = require('body-parser');
const helmet = require('helmet') const helmet = require('helmet');
const morgan = require('morgan') const morgan = require('morgan');
const cors = require('cors') // module to format the json response const cors = require('cors'); // module to format the json response
const dotenv = require('dotenv').config() require('dotenv').config();
// Creates instances of database connections
const connection = require('./database/bookshelf')
const db = connection.db
// CONSTANTS // CONSTANTS
const port = 2814 const port = 2814;
// Import routes // Import routes
const routes = require('./routes') const routes = require('./routes');
// Builds app w/ express // Builds app w/ express
let app = express() let app = express();
app.use(bodyParser.json({ limit: '10kb' })) app.use(bodyParser.json({ limit: '10kb' }));
app.use(cors()) app.use(cors({
app.use(morgan('tiny')) origin: [
app.use(helmet()) "http://localhost:8080",
],
credentials: true,
}));
app.use(morgan('dev'));
app.use(helmet());
// Serves // Server
const server = app.listen( port, () => {console.log(`App listening on port ${port}`)}) app.listen(port, () => console.log(`App listening on port ${port}`));
// Get credentials // Entry route
// app.get('/api/login', (req, res, next) => { app.use('/api/v1/', routes.auth);
// if (req.headers.auracle_key !== process.env.API_KEY_PUBLIC) {
// return res.status(401).send('Credentials error !')
// } else {
// return res.status(200).send(JSON.stringify(
// {
// "secret_key": process.env.API_KEY_PRIVATE,
// })
// )
// }
// })
// Auth guard
const authguard = (req, res, next) => {
if (req.headers.auracle_key !== process.env.API_KEY_PUBLIC) {
return res.status(401).send('The API key is either missing or incorrect.')
} else {
next()
}
}
// Routing // Routing
app.use('/api/spells', authguard,routes.spells) app.use('/api/v1/spells', routes.spells);
app.use('/api/schools', authguard, routes.schools) app.use('/api/v1/schools', routes.schools);
app.use('/api/meta_schools', authguard, routes.meta_schools) app.use('/api/v1/meta_schools', routes.meta_schools);
app.use('/api/variables', authguard, routes.variables) app.use('/api/v1/variables', routes.variables);
app.use('/api/ingredients', authguard, routes.ingredients) app.use('/api/v1/ingredients', routes.ingredients);
app.use('/api/users', authguard, routes.users) app.use('/api/v1/users', routes.users);

View File

@@ -0,0 +1,13 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./user-model');
let APIToken = bookshelf.Model.extend({
tableName: 'api_token',
hidden: ['id'],
user() {
return this.belongsTo('User', 'user_uuid', 'uuid');
}
});
module.exports = bookshelf.model('APIToken', APIToken);

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./role-model');
let Permission = bookshelf.Model.extend({
tableName: 'permission',
hidden: ['id'],
role() {
return this.belongsToMany('Role', 'role_permission');
}
});
module.exports = bookshelf.model('Permission', Permission);

12
api/models/role-model.js Normal file
View File

@@ -0,0 +1,12 @@
const bookshelf = require('../database/bookshelf').bookshelf;
require('./permission-model');
let Role = bookshelf.Model.extend({
tableName: 'role',
permissions() {
return this.belongsToMany('Permission', 'role_permission');
}
});
module.exports = bookshelf.model('Role', Role);

View File

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

View File

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

View File

@@ -1,9 +1,17 @@
'use strict' const bookshelf = require('../database/bookshelf').bookshelf;
const bookshelf = require('../database/bookshelf').bookshelf
require('./role-model');
require('./spell-model');
let User = bookshelf.Model.extend({ let User = bookshelf.Model.extend({
tableName: 'user', tableName: 'user',
hidden: ['id', 'password'] hidden: ['password', 'role_id'],
}) role() {
return this.belongsTo('Role');
},
spells() {
return this.hasMany('Spell', 'author_id');
}
});
module.exports = bookshelf.model('User', User) module.exports = bookshelf.model('User', User);

View File

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

1658
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"description": "API for Auracle database", "description": "API for Auracle database",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"run": "node index.js" "start": "node index.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -32,12 +32,42 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"handlebars": "^4.7.6",
"helmet": "^3.22.0", "helmet": "^3.22.0",
"jsonschema": "^1.2.6", "jsonschema": "^1.2.6",
"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",
"nodemailer": "^6.4.17",
"uuid": "^8.2.0" "uuid": "^8.2.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
"eslint": "^7.18.0",
"eslint-plugin-import": "^2.22.1"
},
"eslintConfig": {
"env": {
"es6": true,
"browser": true
},
"parser": "babel-eslint",
"rules": {
"accessor-pairs": "warn",
"array-callback-return": "error",
"brace-style": "warn",
"consistent-return": "error",
"default-case": "error",
"eqeqeq": "warn",
"no-case-declarations": "error",
"no-empty-pattern": "warn",
"no-fallthrough": "error",
"no-unused-vars": "error",
"no-useless-catch": "error",
"no-var": "error",
"semi": "warn",
"no-extra-semi": "error",
"yoda": "warn"
}
} }
} }

View File

@@ -1,20 +1,16 @@
'use strict'
// Bookshelf // Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/ingredient-model') const model = require('../models/ingredient-model');
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator;
const v = new Validator() const validator = new Validator();
const IngredientValidation = require("../validations/IngredientValidation") const IngredientValidation = require("../validations/IngredientValidation");
v.addSchema(IngredientValidation, "/IngredientValidation") validator.addSchema(IngredientValidation, "/IngredientValidation");
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject const isEmptyObject = require('../functions').isEmptyObject;
// Error handling
const { HttpError } = require('../validations/Errors')
class IngredientRepository { class IngredientRepository {
@@ -23,95 +19,125 @@ class IngredientRepository {
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.fetchAll({ withRelated: ['spells'] }) .fetchAll({ withRelated: ['spells'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get ingredients")) reject({
}) "message": "Il n'existe aucun ingrédient disponible.",
}) "code": 404,
});
});
});
} }
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['spells'] }) .fetch({ withRelated: ['spells'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get ingredient")) reject({
}) "message": "L'ingrédient en question n'a pas pu être trouvé.",
}) "code": 404,
});
});
});
} }
getSpellsFromOne(id) { getSpellsFromOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] }) .fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get ingredient")) reject({
}) "message": "Les sortilèges liés à cet ingrédient n'ont pas pu être récupérés.",
}) "code": 404,
});
});
});
} }
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 (isEmptyObject(igr)) { if (isEmptyObject(igr)) {
reject(new HttpError(403, "Error: Ingredient cannot be nothing !")) reject({
} else if (!v.validate(igr, IngredientValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors)) "code": 403,
});
} else if (!validator.validate(igr, IngredientValidation).valid) {
reject({
"message": `Le modèle d'ingrédient n'est pas respecté : ${validator.validate(s, IngredientValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(igr.description)) { } else if (isXSSAttempt(igr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return model.forge({ return new model({
'name': igr.name, 'name': igr.name,
'description': igr.description, 'description': igr.description,
}).save(null, { }).save(null, {
transacting: t transacting: t
}) })
.catch(err => { .catch(err => {
throw err throw err;
}) });
}) })
.then(v => { .then(v => {
return v.load(['spells']) return v.load(['spells']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Insert Ingredient error")) reject({
}) "message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
} }
}) });
} }
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 (isEmptyObject(igr)) { if (isEmptyObject(igr)) {
reject(new HttpError(403, "Error: Ingredient cannot be nothing !")) reject({
} else if (!v.validate(igr, IngredientValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(igr, IngredientValidation).errors)) "code": 403,
});
} else if (!validator.validate(igr, IngredientValidation).valid) {
reject({
"message": `Le modèle d'ingrédient n'est pas respecté : ${validator.validate(s, IngredientValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(igr.description)) { } else if (isXSSAttempt(igr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
model.forge({id: id}) new model({ id: id })
.fetch({ require: true, withRelated: ['spells'] }) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -123,49 +149,58 @@ class IngredientRepository {
transacting: t transacting: t
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
}) })
.then(v => { .then(v => {
return v.load(['spells']) return v.load(['spells']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Update Ingredient error")) reject({
}) "message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get ingredient")) reject({
}) "message": "L'ingrédient en question n'a pas été trouvé.",
"code": 404,
});
});
} }
}) });
} }
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ require: true, withRelated: ['spells'] }) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
v.spells().detach() v.spells().detach();
v.destroy() v.destroy();
}) })
.then(() => { .then(() => {
resolve({ resolve({
'message': 'Ingredient with ID ' + id + ' successfully deleted !' 'message': 'Ingredient with ID ' + id + ' successfully deleted !'
}) });
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get ingredient")) reject({
}) "message": "L'ingrédient en question n'a pas été trouvé.",
}) "code": 404,
});
});
});
} }
} }
module.exports = IngredientRepository module.exports = IngredientRepository;

View File

@@ -1,19 +1,11 @@
'use strict'
// Bookshelf // Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf
const model = require('../models/meta-school-model') const model = require('../models/meta-school-model')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
const v = new Validator() const validator = new Validator()
const MetaSchoolValidation = require("../validations/MetaSchoolValidation") const MetaSchoolValidation = require("../validations/MetaSchoolValidation")
v.addSchema(MetaSchoolValidation, "/MetaSchoolValidation") validator.addSchema(MetaSchoolValidation, "/MetaSchoolValidation")
// Validations
const regexXSS = RegExp(/<[^>]*script/)
// Error handling
const { HttpError } = require('../validations/Errors')
class MetaSchoolRepository { class MetaSchoolRepository {
@@ -22,21 +14,24 @@ class MetaSchoolRepository {
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.fetchAll({ withRelated: ['schools'] }) .fetchAll({ withRelated: ['schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err)
reject(new HttpError(500, "Couldn't get meta schools")) reject({
"message": "Il n'existe aucune grande école disponible.",
"code": 404
});
}) })
}) })
} }
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['schools'] }) .fetch({ withRelated: ['schools'] })
.then(v => { .then(v => {
@@ -44,7 +39,10 @@ class MetaSchoolRepository {
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err)
reject(new HttpError(500, "Couldn't get meta school")) reject({
"message": "La grande école en question n'a pas pu être trouvée.",
"code": 404
});
}) })
}) })
} }

View File

@@ -1,20 +1,16 @@
'use strict'
// Bookshelf // Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/school-model') const model = require('../models/school-model');
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator;
const v = new Validator() const validator = new Validator();
const SchoolValidation = require("../validations/SchoolValidation") const SchoolValidation = require("../validations/SchoolValidation");
v.addSchema(SchoolValidation, "/SchoolValidation") validator.addSchema(SchoolValidation, "/SchoolValidation");
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject const isEmptyObject = require('../functions').isEmptyObject;
// Error handling
const { HttpError } = require('../validations/Errors')
class SchoolRepository { class SchoolRepository {
@@ -23,60 +19,78 @@ class SchoolRepository {
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.fetchAll({ withRelated: ['meta_schools'] }) .fetchAll({ withRelated: ['meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get schools")) reject({
}) "message": "Il n'existe aucune école disponible.",
}) "code": 404,
});
});
});
} }
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['meta_schools'] }) .fetch({ withRelated: ['meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get school")) reject({
}) "message": "L'école en question n'a pas pu être trouvée.",
}) "code": 404,
});
});
});
} }
getSpellsFromOne(id) { getSpellsFromOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] }) .fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get spells from school")) reject({
}) "message": "Les sortilèges de cette école n'ont pas pu être récupérés.",
}) "code": 404,
});
});
});
} }
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 (isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: School cannot be nothing !")) reject({
} else if (!v.validate(s, SchoolValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors)) "code": 403,
});
} else if (!validator.validate(s, SchoolValidation).valid) {
reject({
"message": `Le modèle d'école n'est pas respecté : ${validator.validate(s, SchoolValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return model.forge({ return new model({
'name': s.name, 'name': s.name,
'description': s.description, 'description': s.description,
'meta_school_id': s.meta_school_id, 'meta_school_id': s.meta_school_id,
@@ -84,34 +98,46 @@ class SchoolRepository {
transacting: t transacting: t
}) })
.catch(err => { .catch(err => {
throw err throw err;
}) });
}) })
.then(v => { .then(v => {
return v.load(['meta_schools']) return v.load(['meta_schools']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Insert School error")) reject({
}) "message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
} }
}) });
} }
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 (isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: School cannot be nothing !")) reject({
} else if (!v.validate(s, SchoolValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SchoolValidation).errors)) "code": 403,
});
} else if (!validator.validate(s, SchoolValidation).valid) {
reject({
"message": `Le modèle d'école n'est pas respecté : ${validator.validate(s, SchoolValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) { } else if (isXSSAttempt(s.name) || isXSSAttempt(s.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
model.forge({id: id}) new model({ id: id })
.fetch({ require: true, withRelated: ['meta_schools'] }) .fetch({ require: true, withRelated: ['meta_schools'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -124,49 +150,58 @@ class SchoolRepository {
transacting: t transacting: t
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
}) })
.then(v => { .then(v => {
return v.load(['meta_schools']) return v.load(['meta_schools']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Update School error")) reject({
}) "message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get school")) reject({
}) "message": "L'école en question n'a pas été trouvée.",
"code": 404,
});
});
} }
}) });
} }
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ require: true, withRelated: ['spells', 'meta_schools'] }) .fetch({ require: true, withRelated: ['spells', 'meta_schools'] })
.then(v => { .then(v => {
v.spells().detach() v.spells().detach();
v.destroy() v.destroy();
}) })
.then(() => { .then(() => {
resolve({ resolve({
'message': 'School with ID ' + id + ' successfully deleted !' 'message': 'School with ID ' + id + ' successfully deleted !'
}) });
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get school")) reject({
}) "message": "L'école en question n'a pas été trouvée.",
}) "code": 404,
});
});
});
} }
} }
module.exports = SchoolRepository module.exports = SchoolRepository;

View File

@@ -5,17 +5,14 @@ const model = require('../models/spell-model')
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator
const v = new Validator() const validator = new Validator()
const SpellValidation = require("../validations/SpellValidation") const SpellValidation = require("../validations/SpellValidation")
v.addSchema(SpellValidation, "/SpellValidation") validator.addSchema(SpellValidation, "/SpellValidation")
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt
const isEmptyObject = require('../functions').isEmptyObject const isEmptyObject = require('../functions').isEmptyObject
// Error handling
const { HttpError } = require('../validations/Errors')
class SpellRepository { class SpellRepository {
constructor() { constructor() {
@@ -24,22 +21,37 @@ class SpellRepository {
getAll(name, description, level, charge, cost, ritual) { getAll(name, description, level, charge, cost, ritual) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let query = model.forge() let query = new model();
if (name) { query.where('name', 'like', `%${name}%`) } if (name) {
if (description) { query.where('description', 'like', `%${description}%`) } query.where('name', 'like', `%${name}%`)
if (level) { query.where({ 'level' : level }) } }
if (charge) { query.where({ 'charge' : charge }) } if (description) {
if (cost) { query.where({ 'cost' : cost }) } query.where('description', 'like', `%${description}%`)
if (ritual) { query.where({ 'is_ritual' : ritual }) } }
if (level) {
query.where({ 'level': level })
}
if (charge) {
query.where({ 'charge': charge })
}
if (cost) {
query.where({ 'cost': cost })
}
if (ritual) {
query.where({ 'is_ritual': ritual })
}
query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients'] }) query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get spells")) reject({
"message": "Il n'existe aucun sortilège disponible.",
"code": 404,
});
}) })
}) })
} }
@@ -47,57 +59,77 @@ class SpellRepository {
getAllPublic(name, description, level, charge, cost, ritual) { getAllPublic(name, description, level, charge, cost, ritual) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let query = model.forge() let query = new model().where({ 'public': 1 })
.where({ 'public' : 1 })
if (name) { query.where('name', 'like', `%${name}%`) } if (name) {
if (description) { query.where('description', 'like', `%${description}%`) } query.where('name', 'like', `%${name}%`)
if (level) { query.where({ 'level' : level }) } }
if (charge) { query.where({ 'charge' : charge }) } if (description) {
if (cost) { query.where({ 'cost' : cost }) } query.where('description', 'like', `%${description}%`)
if (ritual) { query.where({ 'is_ritual' : ritual }) } }
if (level) {
query.where({ 'level': level })
}
if (charge) {
query.where({ 'charge': charge })
}
if (cost) {
query.where({ 'cost': cost })
}
if (ritual) {
query.where({ 'is_ritual': ritual })
}
query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients'] }) query.fetchAll({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get public spells")) reject({
"message": "Il n'existe aucun sortilège disponible.",
"code": 404,
});
}) })
}) })
} }
getPage(page) { getPage(page) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'public': 1 }) .where({ 'public': 1 })
.fetchPage({ .fetchPage({
pageSize: 20, pageSize: 20,
page: page, page: page,
withRelated: ['schools.meta_schools', 'variables', 'ingredients'], withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'],
}) })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get public spells")) reject({
"message": "La page de sortilèges n'a pas pu être chargée",
"code": 404,
});
}) })
}) })
} }
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['schools.meta_schools', 'variables', 'ingredients']}) .fetch({ withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }))
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get spell")) reject({
"message": "Le sortilège en question n'a pas été trouvé.",
"code": 404,
});
}) })
}) })
} }
@@ -106,14 +138,23 @@ class SpellRepository {
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 (isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !")) reject({
} else if (!v.validate(s, SpellValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).errors)) "code": 403,
});
} else if (!validator.validate(s, SpellValidation).valid) {
reject({
"message": `Le modèle de sortilège n'est pas respecté : ${validator.validate(s, SpellValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || 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({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return model.forge({ return new model({
'name': s.name, 'name': s.name,
'description': s.description, 'description': s.description,
'level': s.level, 'level': s.level,
@@ -145,18 +186,25 @@ class SpellRepository {
}); });
}) })
.catch(err => { .catch(err => {
throw err console.log(err);
reject({
"message": "Un attributs du sortilège a provoqué une erreur d'insertion.",
"code": 500,
});
}) })
}) })
.then(v => { .then(v => {
return v.load(['schools.meta_schools', 'variables', 'ingredients']) return v.load(['schools.meta_schools', 'variables', 'ingredients', 'author'])
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id))
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Insert Spell error")) reject({
"message": "Le sortilège n'a pas pu être ajouté.",
"code": 500,
});
}) })
} }
}) })
@@ -166,14 +214,23 @@ class SpellRepository {
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 (isEmptyObject(s)) { if (isEmptyObject(s)) {
reject(new HttpError(403, "Error: Spell cannot be nothing !")) reject({
} else if (!v.validate(s, SpellValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(s, SpellValidation).errors)) "code": 403,
});
} else if (!validator.validate(s, SpellValidation).valid) {
reject({
"message": `Le modèle de sortilège n'est pas respecté : ${validator.validate(s, SpellValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(s.name) || isXSSAttempt(s.description) || 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({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
model.forge({id: id}) new model({ id: id })
.fetch({require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients']}) .fetch({ require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return v.save({ return v.save({
@@ -193,20 +250,21 @@ class SpellRepository {
let schools = spell.related('school'); let schools = spell.related('school');
return spell.schools().detach(schools, { transacting: t }); return spell.schools().detach(schools, { transacting: t });
} }
return return spell
}) })
.tap(spell => { .tap(spell => {
if (s.variables) { if (s.variables) {
let variables = spell.related('variable'); let variables = spell.related('variable');
return spell.variables().detach(variables, { transacting: t }); return spell.variables().detach(variables, { transacting: t });
} }
return return spell;
}) })
.tap(spell => { .tap(spell => {
if (s.ingredients) { if (s.ingredients) {
let ingredients = spell.related('ingredient'); let ingredients = spell.related('ingredient');
return spell.ingredients().detach(ingredients, { transacting: t }); return spell.ingredients().detach(ingredients, { transacting: t });
} }
return spell;
}) })
.tap(spell => { .tap(spell => {
return spell return spell
@@ -230,24 +288,33 @@ class SpellRepository {
}); });
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err reject({
"message": "Un attributs du sortilège a provoqué une erreur d'insertion.",
"code": 500,
})
}) })
}) })
.then(v => { .then(v => {
return v.load(['schools.meta_schools', 'variables', 'ingredients']) return v.load(['schools.meta_schools', 'variables', 'ingredients', 'author']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Update Spell error")) reject({
"message": "Une erreur de chargement est survenue.",
"code": 500,
});
}) })
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get spell")) reject({
"message": "Le sortilège en question n'a pas été trouvé.",
"code": 404,
});
}) })
} }
}) })
@@ -255,23 +322,27 @@ class SpellRepository {
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients']}) .fetch({ require: true, withRelated: ['schools.meta_schools', 'variables', 'ingredients', 'author'] })
.then(v => { .then(v => {
v.schools().detach() v.schools().detach();
v.variables().detach() v.variables().detach();
v.ingredients().detach() v.ingredients().detach();
v.destroy() v.destroy();
}) })
.then(() => { .then(() => {
resolve({ resolve({
'message': 'Spell with ID ' + id + ' successfully deleted !' "message": `Le sortilège #${id} a été supprimé avec succès.`,
}) "code": 200,
});
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get spell")) reject({
"message": "Le sortilège en question n'a pas été trouvé.",
"code": 404,
});
}) })
}) })
} }

View File

@@ -1,127 +1,235 @@
'use strict' 'use strict'
// Bookshelf // Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/user-model') const model = require('../models/user-model');
const token_model = require('../models/api-token-model');
// Hashing and passwords // Hashing and passwords
const bcrypt = require('bcrypt') const bcrypt = require('bcrypt');
const { v4: uuidv4 } = require('uuid') const { v4: uuidv4 } = require('uuid');
// Mailing methods
const mails = require('../smtp/mails');
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator;
const v = new Validator() const validator = new Validator();
const UserValidation = require("../validations/UserValidation") const UserValidation = require("../validations/UserValidation");
v.addSchema(UserValidation, "/UserValidation") validator.addSchema(UserValidation, "/UserValidation");
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject const isEmptyObject = require('../functions').isEmptyObject;
class UserRepository { class UserRepository {
constructor() { constructor() {
} }
/**
* Fetches all users in the dabatase.
*
* @returns { Promise }
* Fulfilled data: Array of user objects.
*/
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.fetchAll() .fetchAll({ withRelated: ['role.permissions'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
})
.catch(() => {
reject({
"message": "Database error, couldn't get all users.",
"code": 500
})
})
})
}
getOneByUUID(uuid, full) {
return new Promise((resolve, reject) => {
model.forge()
.where({ 'uuid' : uuid })
.fetch()
.then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full }))
}) })
.catch(err => { .catch(err => {
console.log(err)
reject({ reject({
"message": "User with this UUID was not found.", "message": "Erreur de base de données, les utilisateurs n'ont pas pu être récupérés.",
"code": 404 "code": 500,
}) });
}) })
}) })
} }
getOneByEmail(mail, full) { /**
* Fetches a user object associated with the uuid.
*
* @param { string } uuid
* @param { boolean } full
* Whether the password should also be fetched. (should never be true unless you want to log the user)
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
getOneByUUID(uuid, full) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge()
.where({ 'mail': mail }) if (!(uuid)) {
.fetch() reject({
"message": "La requête doit renseigner un uuid.",
"code": 400,
})
}
new model()
.where({ 'uuid': uuid })
.fetch({ withRelated: ['role.permissions'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full })) resolve(v.toJSON({ omitPivot: true, visibility: !full }));
}) })
.catch(() => { .catch(() => {
reject({ reject({
"message": "User with this email was not found.", "message": "L'utilisateur avec cet UUID n'a pas été trouvé.",
"code": 404 "code": 404,
}) });
}) })
}) })
} }
/**
* Fetches a user object associated with the mail address.
*
* @param { string } mail
* @param { boolean } full
* Whether the password should also be fetched. (should never be true unless you want to log the user)
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
getOneByEmail(mail, full) {
return new Promise((resolve, reject) => {
if (!(mail)) {
reject({
"message": "La requête doit renseigner un email.",
"code": 400,
})
}
new model()
.where({ 'mail': mail })
.fetch({ withRelated: ['role'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true, visibility: !full }));
})
.catch(() => {
reject({
"message": "L'utilisateur avec cet email n'a pas été trouvé.",
"code": 404,
});
});
})
}
/**
* Fetches all spells linked to a user's uuid
*
* @param { string } uuid
*
* @returns
*/
getSpellsFromOne(uuid) {
return new Promise((resolve, reject) => {
if (!(uuid)) {
reject({
"message": "La requête doit renseigner un uuid.",
"code": 400,
})
}
new model()
.where({ 'uuid': uuid })
.fetch({ withRelated: ['role', 'spells.schools.meta_schools', 'spells.variables', 'spells.ingredients'] })
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(() => {
reject({
"message": "Les sorts liés à cet utilisateur n'ont pas été trouvés.",
"code": 404,
});
});
})
}
/**
* Registers a user based on the model at ./models/user-model.js.
*
* @param { object} u
* User object
*
* @returns { Promise }
* Fulfilled data: Queried user object.
*/
addOne(u) { addOne(u) {
return new Promise(async (resolve, reject) => { return new Promise(async (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 (isEmptyObject(u)) { if (isEmptyObject(u)) {
reject({ reject({
"message": "Request body cannot be empty.", "message": "Le corps de requête ne peut être vide.",
"code": 403 "code": 403,
}) })
} else if (!v.validate(u, UserValidation).valid) { } else if (!validator.validate(u, UserValidation).valid) {
reject({ reject({
"message": "Schema is not valid - " + v.validate(u, UserValidation).errors, "message": "Structure de la requête invalide - " + validator.validate(u, UserValidation).errors,
"code": 403 "code": 403,
}) })
} else if (isXSSAttempt(u.name) || isXSSAttempt(u.password) || isXSSAttempt(u.mail)) { } else if (isXSSAttempt(u.name) || isXSSAttempt(u.password) || isXSSAttempt(u.mail)) {
reject({ reject({
"message": "Injection attempt detected, aborting the request.", "message": "Essai d'injection détecté, avortement de la requête.",
"code": 403 "code": 403,
}) })
} else { } else {
let hash = await bcrypt.hash(u.password, 10) let hash = await bcrypt.hash(u.password, 10);
let uuid = uuidv4()
let uuid = uuidv4();
let verification_token = uuidv4();
this.checkIfEmailAvailable(u.mail) this.checkIfEmailAvailable(u.mail)
.then(() => { .then(() => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return model.forge({ return new model({
'uuid': uuid, 'uuid': uuid,
'name': u.name, 'name': u.name,
'mail': u.mail, 'mail': u.mail,
'password': hash, 'password': hash,
'verification_token': verification_token,
}) })
.save(null, { .save(null, {
transacting: t transacting: t
}) })
.catch(err => { .catch(err => {
throw err console.log(err);
reject({
"message": "Un attribut de l'utilisateur a provoqué une erreur d'insertion.",
"code": 500,
});
}) })
}) })
.then(() => { .then(() => {
return this.getOneByUUID(uuid, false) return this.getOneByUUID(uuid, false)
}) })
.then(newUser => { .then(newUser => {
// Send a mail to the new user's email with a link to verification url
mails.sendRegistrationMail({
user: {
name: newUser.name,
mail: newUser.mail,
token: verification_token,
}
});
// Then resolves the api call
resolve({ resolve({
"message": "Account successfully created !", "message": `Compte utilisateur #${newUser.id} créé avec succès.`,
"code": 201,
"user": newUser, "user": newUser,
}) });
}) })
.catch(err => { .catch(err => {
console.log(err);
resolve({ resolve({
"message": "An error has occured while creating your account.", "message": "Une erreur s'est produite en créant votre compte. Veuillez réessayer ultérieurement ou contactez l'administrateur.",
"code": 500, "code": 500,
}) })
}) })
@@ -133,47 +241,321 @@ class UserRepository {
}) })
} }
// Log user with an email address and a password /**
* Verifies an account based on a private UUID token
*
* @param { string } token
* A UUID v4 identification token provided at registration and on special demands
*
* @returns Redirects to login page
*/
verifyUser(token) {
return new Promise((resolve, reject) => {
new model()
.where({ 'verification_token': token })
.fetch()
.then(v => {
bookshelf.transaction(t => {
return v.save({
'verification_token': null,
'verified': 1,
}, {
method: 'update',
transacting: t
})
})
.then(() => {
resolve({
"message": "Insérez ici une future redirection vers le client.",
"code": 202,
})
})
})
.catch(() => {
reject({
"message": "Le lien de vérification ne semble pas correct.",
"code": 404,
})
})
});
}
/**
* Logs a user by comparing the dual mail/password inputs
*
* @param { string } mail
* @param { string } password
*
* @return { Promise }
* Fulfilled data: Queried user object.
*/
logUser(mail, password) { logUser(mail, password) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.getOneByEmail(mail, true) this.getOneByEmail(mail, true)
.then(async fetchedUser => { .then(async fetchedUser => {
let match = await bcrypt.compare(password, fetchedUser.password) let match = await bcrypt.compare(password, fetchedUser.password);
delete fetchedUser.id
delete fetchedUser.password
// Makes sure no hash gets out
delete fetchedUser.password;
// If you found a user...
if (match) { if (match) {
// If they're banned...
if (fetchedUser.banned) {
reject({
"message": `L'utilisateur #${fetchedUser.name} a été banni, la connexion est impossible.`,
"code": 403,
});
// If they're not verified...
} else if (!fetchedUser.verified) {
reject({
"message": `L'utilisateur #${fetchedUser.name} n'as pas été vérifié, le compte doit être activé avant la connexion.`,
"code": 401,
});
} else {
resolve({ resolve({
"message": "User successfully logged in !", "message": `L'utilisateur #${fetchedUser.name} s'est connecté.`,
"code": 200,
"user": fetchedUser, "user": fetchedUser,
}) });
}
} else { } else {
reject({ reject({
"message": "Les informations de connexion sont erronées.", "message": "Les informations de connexions sont erronées.",
}) "code": 400,
});
} }
}) })
.catch(err => { .catch(err => {
reject(err) reject(err);
}) })
}) })
} }
// Check if one user already has that email /**
* Generate a token for the user to use the API
* Requires mail and password for verifying the user
*
* @param { string } mail
* @param { string } password
*
* @returns { Promise }
* Fulfilled data: A unique UUID token string.
*/
genAPIToken(mail, password) {
return new Promise((resolve, reject) => {
this.logUser(mail, password)
.then(v => {
let user = v.user;
let new_token = uuidv4();
bookshelf.transaction(t => {
return new token_model({
'value': new_token,
'user_uuid': user.uuid,
})
.save(null, {
transacting: t
})
.catch(err => {
// If the account already has an API key linked...
if (err.errno === 1062) {
this.fetchAPIKey(user.uuid)
.then(old_api_key => {
reject({
"message": "Votre compte a déjà généré une clé d'API.",
"code": 409,
"API_key": old_api_key.value,
});
});
// Default errors
} else {
throw err
}
});
})
.then(api_key => {
resolve({
"message": "La clé d'API a été généré.",
"code": 201,
"API_key": api_key,
})
})
.catch(err => {
console.log(err);
reject({
"message": "La génération de jeton d'API n'a pas pu être conclue.",
"code": 500,
});
})
})
.catch(err => {
reject(err);
})
})
}
checkAPITokenPerms(token, permissions) {
return new Promise(async (resolve, reject) => {
// If the request doesn't have a token, reject it.
if (!token) {
reject({
"message": "Vous devez utiliser un jeton d'API dans l'en-tête de votre requête.",
"code": 401,
})
}
// Fetches user from token
new token_model()
.where({ 'value': token })
.fetch({ withRelated: 'user.role.permissions' })
// Catches not found errors
.catch(err => {
if (err instanceof token_model.NotFoundError) {
reject({
"message": "Ce jeton n'est affilié à aucun compte.",
"code": 404,
});
} else {
reject({
"message": "Le jeton ou l'utilisateur n'a pas pu être récupéré.",
"code": 500,
});
}
})
.then(fullToken => {
token = fullToken.toJSON({ omitPivot: true });
// If the token is associated with a banned user...
if (token.user.banned) {
reject({
"message": "Le jeton est lié à un utilisateur banni, il n'est donc plus utilisable.",
"code": 401,
});
}
// If the token is associated with a non-verified user...
if (!token.user.verified) {
reject({
"message": "Le jeton est lié à un utilisateur non-vérifié, il n'est donc pas utilisable.",
"code": 401,
});
}
let user_permissions = token.user.role.permissions;
// Convert user_perm to array
user_permissions.forEach((user_perm, i) => {
user_permissions[i] = user_perm.slug;
});
// Loops to check if the person has all the permissions required...
permissions.forEach(perm => {
// If not, reject...
if (!user_permissions.includes(perm)) {
reject({
"message": "Permissions insuffisantes.",
"code": 401
})
}
});
// If everything went well, resolve the promise.
resolve({
"message": "Credentials accepted.",
"code": 200
});
})
// Unhandled errors
.catch(() => {
reject({
"message": "Une erreur inconnue est survenue.",
"code": 500,
});
});
})
}
/**
* Check if the email that was input is available for account creation.
*
* @param {string} mail
*
* @returns { Promise }
* Fulfilled: HTTP 200 if email is available.
* Rejected: HTTP 400-409 if email is already used.
*/
checkIfEmailAvailable(mail) { checkIfEmailAvailable(mail) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!(mail)) {
reject({
"message": "La requête doit renseigner un email.",
"code": 400,
})
}
if (!this.validateMail(mail)) {
reject({
"message": "La requête n'est pas un email valide.",
"code": 400,
})
}
this.getOneByEmail(mail, false) this.getOneByEmail(mail, false)
.then(() => { .then(() => {
reject({ reject({
"message": "L'email est déjà utilisé par un autre utilisateur.", "message": "Cet email est déjà lié à un compte.",
"code": 403 "code": 409,
}) })
}) })
.catch(() => { .catch(() => {
resolve(true) resolve({
"message": "Cet email est disponible.",
"code": 200,
}) })
}) })
})
}
/**
* Fetches the associated api_token from a user uuid.
*
* @param { string } uuid
*
* @returns { Promise }
* Fulfilled data: Queried API token object
*/
fetchAPIKey(uuid) {
return new Promise((resolve, reject) => {
new token_model()
.where({ 'user_uuid': uuid })
.fetch()
.then(v => {
resolve(v.toJSON({ omitPivot: true }));
})
.catch(err => {
reject(err);
})
})
}
/**
* Whether a mail is correctly formed and ripe for receiving, ie: xxx@yyy.zzz.
*
* @param { string } mail
*
* @returns { boolean }
*/
validateMail(mail) {
const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regex.test(String(mail).toLowerCase());
} }
} }

View File

@@ -1,20 +1,17 @@
'use strict'
// Bookshelf // Bookshelf
const bookshelf = require('../database/bookshelf').bookshelf const bookshelf = require('../database/bookshelf').bookshelf;
const model = require('../models/variable-model') const model = require('../models/variable-model');
// Model validation // Model validation
const Validator = require('jsonschema').Validator const Validator = require('jsonschema').Validator;
const v = new Validator() const validator = new Validator();
const VariableValidation = require("../validations/VariableValidation") const VariableValidation = require("../validations/VariableValidation");
v.addSchema(VariableValidation, "/VariableValidation") validator.addSchema(VariableValidation, "/VariableValidation");
// Validations // Validations
const isXSSAttempt = require('../functions').isXSSAttempt const isXSSAttempt = require('../functions').isXSSAttempt;
const isEmptyObject = require('../functions').isEmptyObject const isEmptyObject = require('../functions').isEmptyObject;
// Error handling
const { HttpError } = require('../validations/Errors')
class VariableRepository { class VariableRepository {
constructor() { constructor() {
@@ -22,94 +19,124 @@ class VariableRepository {
getAll() { getAll() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.fetchAll({ withRelated: ['spells'] }) .fetchAll({ withRelated: ['spells'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get variables")) reject({
}) "message": "Il n'existe aucune variable disponible.",
}) "code": 404,
});
});
});
} }
getOne(id) { getOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['spells'] }) .fetch({ withRelated: ['spells'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get variable")) reject({
}) "message": "La variable en question n'a pas pu être trouvée.",
}) "code": 404,
});
});
});
} }
getSpellsFromOne(id) { getSpellsFromOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] }) .fetch({ withRelated: ['spells', 'spells.schools', 'spells.variables', 'spells.ingredients', 'spells.schools.meta_schools'] })
.then(v => { .then(v => {
resolve(v.toJSON({ omitPivot: true })) resolve(v.toJSON({ omitPivot: true }));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Couldn't get variable")) reject({
}) "message": "Les sortilèges liés à cette variable n'ont pas pu être récupérés.",
}) "code": 404,
});
});
});
} }
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 (isEmptyObject(vr)) { if (isEmptyObject(vr)) {
reject(new HttpError(403, "Error: Variable cannot be nothing !")) reject({
} else if (!v.validate(vr, VariableValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors)) "code": 403,
});
} else if (!validator.validate(vr, VariableValidation).valid) {
reject({
"message": `Le modèle de variable n'est pas respecté : ${validator.validate(s, VariableValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(vr.description)) { } else if (isXSSAttempt(vr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
bookshelf.transaction(t => { bookshelf.transaction(t => {
return model.forge({ return new model({
'description': vr.description, 'description': vr.description,
}).save(null, { }).save(null, {
transacting: t transacting: t
}) })
.catch(err => { .catch(err => {
throw err throw err;
}) });
}) })
.then(v => { .then(v => {
return v.load(['spells']) return v.load(['spells']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Insert Variable error")) reject({
}) "message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
} }
}) });
} }
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 (isEmptyObject(vr)) { if (isEmptyObject(vr)) {
reject(new HttpError(403, "Error: Variable cannot be nothing !")) reject({
} else if (!v.validate(vr, VariableValidation).valid) { "message": "Le corps de la requête ne peut pas être vide.",
reject(new HttpError(403, "Error: Schema is not valid - " + v.validate(vr, VariableValidation).errors)) "code": 403,
});
} else if (!validator.validate(vr, VariableValidation).valid) {
reject({
"message": `Le modèle de variable n'est pas respecté : ${validator.validate(s, VariableValidation).errors}`,
"code": 403,
});
} else if (isXSSAttempt(vr.description)) { } else if (isXSSAttempt(vr.description)) {
reject(new HttpError(403, 'Injection attempt detected, aborting the request.')) reject({
"message": "Tentative d'injection XSS détectée, requête refusée.",
"code": 403,
});
} else { } else {
model.forge({id: id}) new model({ id: id })
.fetch({ require: true, withRelated: ['spells'] }) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
bookshelf.transaction(t => { bookshelf.transaction(t => {
@@ -120,49 +147,58 @@ class VariableRepository {
transacting: t transacting: t
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
}) })
.then(v => { .then(v => {
return v.load(['spells']) return v.load(['spells']);
}) })
.then(v => { .then(v => {
resolve(this.getOne(v.id)) resolve(this.getOne(v.id));
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(500, "Update Variable error")) reject({
}) "message": "Une erreur d'insertion s'est produite.",
"code": 500,
});
});
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get variable")) reject({
}) "message": "La variable en question n'a pas été trouvée.",
"code": 404,
});
});
} }
}) });
} }
deleteOne(id) { deleteOne(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
model.forge() new model()
.where({ 'id': id }) .where({ 'id': id })
.fetch({ require: true, withRelated: ['spells'] }) .fetch({ require: true, withRelated: ['spells'] })
.then(v => { .then(v => {
v.spells().detach() v.spells().detach();
v.destroy() v.destroy();
}) })
.then(() => { .then(() => {
resolve({ resolve({
'message': 'Variable with ID ' + id + ' successfully deleted !' 'message': 'Variable with ID ' + id + ' successfully deleted !'
}) });
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
reject(new HttpError(404, "Couldn't get variable")) reject({
}) "message": "La variable en question n'a pas été trouvée.",
}) "code": 404,
});
});
});
} }
} }
module.exports = VariableRepository module.exports = VariableRepository;

28
api/routes/auth.js Normal file
View File

@@ -0,0 +1,28 @@
// Router
const express = require('express');
let router = express.Router();
// Repository
const UserRepository = require('../repositories/user-repository');
const Users = new UserRepository();
// ROUTES
// GEN API TOKEN
const generateAPIToken = (mail, password) => {
return Users.genAPIToken(mail, password)
.catch(err => {
throw err;
});
};
router.get('/genToken', async (req, res) => {
generateAPIToken(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(err));
});
});
module.exports = router;

View File

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

View File

@@ -1,31 +1,33 @@
'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
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();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getIngredients = () => { const getIngredients = () => {
return Ingredients.getAll() return Ingredients.getAll()
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/', async (req, res) => { router.get(
'/',
async (req, res) => {
getIngredients() getIngredients()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -33,24 +35,26 @@ router.get('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET ONE ------------------ // GET ONE ------------------
const getIngredient = (id) => { const getIngredient = (id) => {
return Ingredients.getOne(id) return Ingredients.getOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getIngredient(req.params.id) getIngredient(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -58,24 +62,26 @@ router.get('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET SPELLS FROM ONE ------------------ // GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => { const getSpellsFromOne = (id) => {
return Ingredients.getSpellsFromOne(id) return Ingredients.getSpellsFromOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/spells', async (req, res) => { router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(req.params.id) getSpellsFromOne(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -83,24 +89,27 @@ router.get('/:id/spells', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// CREATE ONE ------------------ // CREATE ONE ------------------
const addIngredient = (igr) => { const addIngredient = (igr) => {
return Ingredients.addOne(igr) return Ingredients.addOne(igr)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_INGREDIENTS']),
async (req, res) => {
addIngredient(req.body) addIngredient(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -108,24 +117,27 @@ router.post('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// UPDATE ONE ------------------ // UPDATE ONE ------------------
const updateIngredient = (id, igr) => { const updateIngredient = (id, igr) => {
return Ingredients.updateOne(id, igr) return Ingredients.updateOne(id, igr)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_INGREDIENTS', 'MODIFY_INGREDIENTS']),
async (req, res) => {
updateIngredient(req.params.id, req.body) updateIngredient(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -133,24 +145,27 @@ router.put('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// DELETE ONE ------------------ // DELETE ONE ------------------
const deleteIngredient = (id) => { const deleteIngredient = (id) => {
return Ingredients.deleteOne(id) return Ingredients.deleteOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_INGREDIENTS', 'MODIFY_INGREDIENTS', 'DELETE_INGREDIENTS']),
async (req, res) => {
deleteIngredient(req.params.id) deleteIngredient(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -158,11 +173,11 @@ router.delete('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// Param validations // Param validations
router.param('id', functions.paramIntCheck) router.param('id', functions.paramIntCheck);
module.exports = router module.exports = router;

View File

@@ -1,12 +1,9 @@
'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // Functions
const connection = require('../database/bookshelf') const functions = require('../functions');
const functions = require('../functions')
// Repository // Repository
const MetaSchoolRepository = require('../repositories/meta-school-repository'); const MetaSchoolRepository = require('../repositories/meta-school-repository');
@@ -17,15 +14,15 @@ const MetaSchools = new MetaSchoolRepository();
const getMetaSchools = () => { const getMetaSchools = () => {
return MetaSchools.getAll() return MetaSchools.getAll()
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/', async (req, res) => { router.get('/', async (req, res) => {
getMetaSchools() getMetaSchools()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -33,24 +30,24 @@ router.get('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET ONE ------------------ // GET ONE ------------------
const getMetaSchool = (id) => { const getMetaSchool = (id) => {
return MetaSchools.getOne(id) return MetaSchools.getOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/', async (req, res) => { router.get('/:id/', async (req, res) => {
getMetaSchool(req.params.id) getMetaSchool(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -58,11 +55,11 @@ router.get('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// Param validations // Param validations
router.param('id', functions.paramIntCheck) router.param('id', functions.paramIntCheck);
module.exports = router module.exports = router;

View File

@@ -0,0 +1,23 @@
// Repository
const UserRepository = require('../../repositories/user-repository');
const Users = new UserRepository();
// AUTHGUARD
const authGuard = (permissions) => {
return async (req, res, next) => {
// Get token from headers
let api_token = req.headers['auracle_key'];
// Uses repo to validate the associated perms with the token
Users.checkAPITokenPerms(api_token, permissions)
.then(() => {
next();
})
.catch(err => {
res.status(err.code).send(JSON.stringify(err));
});
};
};
module.exports = authGuard;

View File

@@ -1,31 +1,33 @@
'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
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();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getSchools = () => { const getSchools = () => {
return Schools.getAll() return Schools.getAll()
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/', async (req, res) => { router.get(
'/',
async (req, res) => {
getSchools() getSchools()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -33,24 +35,26 @@ router.get('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET ONE ------------------ // GET ONE ------------------
const getSchool = (id) => { const getSchool = (id) => {
return Schools.getOne(id) return Schools.getOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getSchool(req.params.id) getSchool(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -58,24 +62,26 @@ router.get('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET SPELLS FROM ONE ------------------ // GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => { const getSpellsFromOne = (id) => {
return Schools.getSpellsFromOne(id) return Schools.getSpellsFromOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/spells', async (req, res) => { router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(req.params.id) getSpellsFromOne(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -83,24 +89,27 @@ router.get('/:id/spells', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// CREATE ONE ------------------ // CREATE ONE ------------------
const addSchool = (s) => { const addSchool = (s) => {
return Schools.addOne(s) return Schools.addOne(s)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_SCHOOL']),
async (req, res) => {
addSchool(req.body) addSchool(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -108,23 +117,26 @@ router.post('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// UPDATE ONE ------------------ // UPDATE ONE ------------------
const updateSchool = (id, s) => { const updateSchool = (id, s) => {
return Schools.updateOne(id, s) return Schools.updateOne(id, s)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_SCHOOLS', 'MODIFY_SCHOOLS']),
async (req, res) => {
updateSchool(req.params.id, req.body) updateSchool(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -132,24 +144,27 @@ router.put('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// DELETE ONE ------------------ // DELETE ONE ------------------
const deleteSchool = (id) => { const deleteSchool = (id) => {
return Schools.deleteOne(id) return Schools.deleteOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_SCHOOLS', 'MODIFY_SCHOOLS', 'DELETE_SCHOOLS']),
async (req, res) => {
deleteSchool(req.params.id) deleteSchool(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -157,11 +172,11 @@ router.delete('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// Param validations // Param validations
router.param('id', functions.paramIntCheck) router.param('id', functions.paramIntCheck);
module.exports = router module.exports = router;

View File

@@ -1,31 +1,33 @@
'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
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();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL PUBLIC ------------------ // GET ALL PUBLIC ------------------
const getPublicSpells = (name, description, level, charge, cost, ritual) => { const getPublicSpells = (name, description, level, charge, cost, ritual) => {
return Spells.getAllPublic(name, description, level, charge, cost, ritual) return Spells.getAllPublic(name, description, level, charge, cost, ritual)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('//:name?/:description?/:level?/:charge?/:cost?/:ritual?/', async (req, res) => { router.get(
'//:name?/:description?/:level?/:charge?/:cost?/:ritual?/',
async (req, res) => {
getPublicSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual) getPublicSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -33,23 +35,26 @@ router.get('//:name?/:description?/:level?/:charge?/:cost?/:ritual?/', async (re
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET ALL ------------------ // GET ALL ------------------
const getSpells = (name, description, level, charge, cost, ritual) => { const getSpells = (name, description, level, charge, cost, ritual) => {
return Spells.getAll(name, description, level, charge, cost, ritual) return Spells.getAll(name, description, level, charge, cost, ritual)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/private/:name?/:description?/:level?/:charge?/:cost?/:ritual?/', async (req, res) => { router.get(
'/private/:name?/:description?/:level?/:charge?/:cost?/:ritual?/',
authGuard(['SECRET_SPELLS']),
async (req, res) => {
getSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual) getSpells(req.query.name, req.query.description, req.query.level, req.query.charge, req.query.cost, req.query.ritual)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -57,23 +62,25 @@ router.get('/private/:name?/:description?/:level?/:charge?/:cost?/:ritual?/', as
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET SOME ------------------ // GET SOME ------------------
const getSomeSpells = (page) => { const getSomeSpells = (page) => {
return Spells.getPage(page) return Spells.getPage(page)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/page/:page', async (req, res) => { router.get(
'/page/:page',
async (req, res) => {
getSomeSpells(req.params.page) getSomeSpells(req.params.page)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -81,23 +88,25 @@ router.get('/page/:page', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET ONE ------------------ // GET ONE ------------------
const getSpell = (id) => { const getSpell = (id) => {
return Spells.getOne(id) return Spells.getOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getSpell(req.params.id) getSpell(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -105,24 +114,27 @@ router.get('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// CREATE ONE ------------------ // CREATE ONE ------------------
const addSpell = (s) => { const addSpell = (s) => {
return Spells.addOne(s) return Spells.addOne(s)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_SPELLS']),
async (req, res) => {
addSpell(req.body) addSpell(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -130,24 +142,27 @@ router.post('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// UPDATE ONE ------------------ // UPDATE ONE ------------------
const updateSpell = (id, s) => { const updateSpell = (id, s) => {
return Spells.updateOne(id, s) return Spells.updateOne(id, s)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_SPELLS', 'MODIFY_SPELLS']),
async (req, res) => {
updateSpell(req.params.id, req.body) updateSpell(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -155,24 +170,27 @@ router.put('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// DELETE ONE ------------------ // DELETE ONE ------------------
const deleteSpell = (id) => { const deleteSpell = (id) => {
return Spells.deleteOne(id) return Spells.deleteOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_SPELLS', 'MODIFY_SPELLS', 'DELETE_SPELLS']),
async (req, res) => {
deleteSpell(req.params.id) deleteSpell(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -180,12 +198,12 @@ router.delete('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// Param validations // Param validations
router.param('id', functions.paramIntCheck) router.param('id', functions.paramIntCheck);
router.param('page', functions.paramIntCheck) router.param('page', functions.paramIntCheck);
module.exports = router module.exports = router;

View File

@@ -1,12 +1,9 @@
'use strict'
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') // const authGuard = require('./middleware/authGuard');
const db = connection.db
// Repository // Repository
const UserRepository = require('../repositories/user-repository'); const UserRepository = require('../repositories/user-repository');
@@ -17,15 +14,14 @@ const Users = new UserRepository();
const getUsers = () => { const getUsers = () => {
return Users.getAll() return Users.getAll()
.catch(err => { .catch(err => {
console.log(err) throw err;
throw err });
}) };
}
router.get('/', async (req, res) => { router.get('/', async (req, res) => {
getUsers() getUsers()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -33,24 +29,23 @@ router.get('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET ONE FORM UUID ------------------ // GET ONE FROM UUID ------------------
const getUserByUUID = (uuid) => { const getUserByUUID = (uuid) => {
return Users.getOneByUUID(uuid) return Users.getOneByUUID(uuid)
.catch(err => { .catch(err => {
console.log(err) throw err;
throw err });
}) };
}
router.get('/:uuid/', async (req, res) => { router.get('/:uuid/', async (req, res) => {
getUserByUUID(req.params.uuid) 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));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -58,24 +53,22 @@ router.get('/:uuid/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET SPELLS FROM ONE ------------------
// LOG A USER ------------------ const getSpellsFromUser = (uuid) => {
const logUser = (mail, password) => { return Users.getSpellsFromOne(uuid)
return Users.logUser(mail, password)
.catch(err => { .catch(err => {
console.log(err) throw err;
throw err });
}) };
} router.get('/:uuid/spells', async (req, res) => {
router.post('/login', async (req, res) => { getSpellsFromUser(req.params.uuid)
logUser(req.body.mail, req.body.password)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -83,22 +76,47 @@ router.post('/login', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
});
});
// CHECK IF MAIL IS AVAILABLE ------------------
const checkIfEmailAvailable = (mail) => {
return Users.checkIfEmailAvailable(mail)
.catch(err => {
throw err;
});
};
router.get('/available/:mail/', async (req, res) => {
checkIfEmailAvailable(req.params.mail)
.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 ------------------ // CREATE ONE ------------------
const addUser = (u) => { const addUser = (u) => {
return Users.addOne(u) return Users.addOne(u)
.catch(err => { .catch(err => {
throw err throw err;
}) });
} };
router.post('/', async (req, res) => { router.post('/', async (req, res) => {
addUser(req.body) addUser(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -106,8 +124,55 @@ router.post('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
module.exports = router
// VERIFY A USER
const verifyUser = (token) => {
return Users.verifyUser(token)
.catch(err => {
throw err;
});
};
router.get('/verification/:token', async (req, res) => {
verifyUser(req.params.token)
.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
})
);
});
});
// LOG A USER ------------------
const logUser = (mail, password) => {
return Users.logUser(mail, password)
.catch(err => {
throw 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
})
);
});
});
module.exports = router;

View File

@@ -1,31 +1,35 @@
'use strict' 'use strict';
// Router // Router
const express = require('express') const express = require('express');
let router = express.Router() let router = express.Router();
// Connection // AuthGuard
const connection = require('../database/bookshelf') const authGuard = require('./middleware/authGuard');
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();
// Functions
const functions = require('../functions');
// ROUTES // ROUTES
// GET ALL ------------------ // GET ALL ------------------
const getvariables = () => { const getvariables = () => {
return Variables.getAll() return Variables.getAll()
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/', async (req, res) => { router.get(
'/',
async (req, res) => {
getvariables() getvariables()
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -33,24 +37,26 @@ router.get('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET ONE ------------------ // GET ONE ------------------
const getVariable = (id) => { const getVariable = (id) => {
return Variables.getOne(id) return Variables.getOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/', async (req, res) => { router.get(
'/:id/',
async (req, res) => {
getVariable(req.params.id) getVariable(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -58,24 +64,26 @@ router.get('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// GET SPELLS FROM ONE ------------------ // GET SPELLS FROM ONE ------------------
const getSpellsFromOne = (id) => { const getSpellsFromOne = (id) => {
return Variables.getSpellsFromOne(id) return Variables.getSpellsFromOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.get('/:id/spells', async (req, res) => { router.get(
'/:id/spells',
async (req, res) => {
getSpellsFromOne(req.params.id) getSpellsFromOne(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.end(JSON.stringify(v)) res.end(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -83,24 +91,27 @@ router.get('/:id/spells', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// CREATE ONE ------------------ // CREATE ONE ------------------
const addVariable = (vr) => { const addVariable = (vr) => {
return Variables.addOne(vr) return Variables.addOne(vr)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.post('/', async (req, res) => { router.post(
'/',
authGuard(['SUBMIT_VARIABLES']),
async (req, res) => {
addVariable(req.body) addVariable(req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -108,24 +119,27 @@ router.post('/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// UPDATE ONE ------------------ // UPDATE ONE ------------------
const updateVariable = (id, vr) => { const updateVariable = (id, vr) => {
return Variables.updateOne(id, vr) return Variables.updateOne(id, vr)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.put('/:id/', async (req, res) => { router.put(
'/:id/',
authGuard(['SUBMIT_VARIABLES', 'MODIFY_VARIABLES']),
async (req, res) => {
updateVariable(req.params.id, req.body) updateVariable(req.params.id, req.body)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -133,24 +147,27 @@ router.put('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// DELETE ONE ------------------ // DELETE ONE ------------------
const deleteVariable = (id) => { const deleteVariable = (id) => {
return Variables.deleteOne(id) return Variables.deleteOne(id)
.catch(err => { .catch(err => {
console.log(err) console.log(err);
throw err throw err;
}) });
} };
router.delete('/:id/', async (req, res) => { router.delete(
'/:id/',
authGuard(['SUBMIT_VARIABLES', 'MODIFY_VARIABLES', 'DELETE_VARIABLES']),
async (req, res) => {
deleteVariable(req.params.id) deleteVariable(req.params.id)
.then(v => { .then(v => {
res.setHeader('Content-Type', 'application/json;charset=utf-8') res.setHeader('Content-Type', 'application/json;charset=utf-8');
res.send(JSON.stringify(v)) res.send(JSON.stringify(v));
}) })
.catch(err => { .catch(err => {
res.status(err.code).send(JSON.stringify( res.status(err.code).send(JSON.stringify(
@@ -158,11 +175,11 @@ router.delete('/:id/', async (req, res) => {
"error": err.message, "error": err.message,
"code": err.code "code": err.code
}) })
) );
}) });
}) });
// Param validations // Param validations
router.param('id', functions.paramIntCheck) router.param('id', functions.paramIntCheck);
module.exports = router module.exports = router;

14
api/smtp/config.js Normal file
View File

@@ -0,0 +1,14 @@
const nodemailer = require('nodemailer');
const transport = nodemailer.createTransport({
host: "smtp.mailtrap.io",
port: 2525,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
}
});
module.exports = {
transport,
};

61
api/smtp/mails.js Normal file
View File

@@ -0,0 +1,61 @@
const smtp = require('./config');
const fs = require('fs');
const handlebars = require('handlebars');
// Sender address for mail service
const sender = 'tymos@auracle.io';
// Fetches a HTML template file for parsing
const getTemplateFile = (path) => {
return new Promise((resolve, reject) => {
fs.readFile(path, { encoding: 'utf-8' }, (err, html) => {
if (err) {
reject(err);
} else {
resolve(html);
}
});
});
};
/**
* SEND REGISTRATION MAIL FUNCTION
* @param {Object} data
* - user
* - name
* - mail
* - token
*/
const sendRegistrationMail = (data) => {
getTemplateFile(__dirname + '/templates/template-sign-up.html')
.then(html => {
let template = handlebars.compile(html);
let template_parsed = template(data);
let message = {
from: sender,
to: data.user.mail,
subject: 'Inscription Auracle.io',
html: template_parsed,
};
smtp.transport.sendMail(message, (err, info) => {
if (err) {
throw err;
} else {
console.log(info);
}
});
})
.catch(err => {
console.log(err);
});
};
// const sendBanEmail = (date) => {
// return null;
// };
module.exports = {
sendRegistrationMail,
};

View File

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<title>Template Email Auracle Inscription</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&family=Playfair+Display:wght@700;800;900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--black: 52, 58, 64;
--white: #FFF;
--primary: 89, 158, 244;
}
body {
color: rgb(var(--black));
font-family: 'Lato', sans-serif;
max-width: 100%;
}
.display {
font-family: 'Playfair Display', serif;
}
h1 {
font-size: 32px;
}
p:not(:last-child) {
margin-bottom: 15px;
}
.italics {
font-style: italic;
}
.wrapper {
position: relative;
padding: 10vh 5vw;
background: url('https://i.imgur.com/IxEKKEY.png') center center fixed repeat;
}
.wrapper:before {
display: block;
content: '';
position: absolute;
top: 0;
height: 120px;
left: 0;
right: 0;
background-color: rgb(var(--primary));
z-index: 0;
}
.container {
position: relative;
background-color: var(--white);
z-index: 5;
}
header,
footer {
text-align: center;
color: var(--white);
background-color: rgb(var(--black));
}
header {
padding: 15px;
}
main {
padding: 30px;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="container">
<header>
<h1 class="display">Bienvenue sur Auracle, {{ user.name }}!</h1>
</header>
<main>
<p>Votre compte Auracle a bien été enregistré et est rattaché à cette adresse mail.</p>
<p>Cependant, afin de garantir la sécurité de vos données, votre compte doit être vérifié avant votre
première connexion. Vous pouvez
<a href="http://localhost:2814/api/v1/users/verification/{{ user.token }}">
suivre ce lien afin de confirmer votre inscription.
</a>
</p>
<p>Si vous rencontrez des problèmes, n'hésitez pas à envoyer un mail à
<a href="mailto:support@auracle.io">
support@auracle.io
</a>
</p>
<p><strong>Merci de votre soutien !</strong></p>
<p class="italics">Bien sincèrement, Izàc Tymos.</p>
</main>
<footer>
<div class="icon">
</div>
</footer>
</div>
</div>
</body>
</html>

View File

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

View File

@@ -6,6 +6,6 @@ const Ingredient = {
"description": { "type": "string" } "description": { "type": "string" }
}, },
"required": ["name", "description"] "required": ["name", "description"]
} };
module.exports = Ingredient module.exports = Ingredient;

View File

@@ -7,6 +7,6 @@ const MetaSchool = {
"schools": { "type": "array" } "schools": { "type": "array" }
}, },
"required": ["name", "description"] "required": ["name", "description"]
} };
module.exports = MetaSchool module.exports = MetaSchool;

View File

@@ -7,6 +7,6 @@ const School = {
"meta_school_id": { "type": "number" }, "meta_school_id": { "type": "number" },
}, },
"required": ["name", "description", "meta_school_id"] "required": ["name", "description", "meta_school_id"]
} };
module.exports = School module.exports = School;

View File

@@ -34,6 +34,6 @@ const Spell = {
} }
}, },
"required": ["name", "description"] "required": ["name", "description"]
} };
module.exports = Spell module.exports = Spell;

View File

@@ -7,6 +7,6 @@ const User = {
"password": { "type": "string" }, "password": { "type": "string" },
}, },
"required": ["name", "password", "mail"] "required": ["name", "password", "mail"]
} };
module.exports = User module.exports = User;

View File

@@ -5,6 +5,6 @@ const Variable = {
"description": { "type": "string" }, "description": { "type": "string" },
}, },
"required": ["description"] "required": ["description"]
} };
module.exports = Variable module.exports = Variable;

33
client/.eslintrc.json Normal file
View File

@@ -0,0 +1,33 @@
{
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"impliedStrict": true
}
},
"extends": [
"plugin:vue/vue3-recommended",
"eslint:recommended"
],
"rules": {
"indent": ["error", 2],
"accessor-pairs": "warn",
"array-callback-return": "error",
"brace-style": "warn",
"consistent-return": "error",
"default-case": "error",
"eqeqeq": "warn",
"no-case-declarations": "error",
"no-empty-pattern": "warn",
"no-fallthrough": "error",
"no-unused-vars": "error",
"no-useless-catch": "error",
"no-var": "error",
"semi": "warn",
"no-extra-semi": "error",
"yoda": "warn"
}
}

View File

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

532
client/package-lock.json generated
View File

@@ -1333,12 +1333,36 @@
"webpack-merge": "^4.2.2" "webpack-merge": "^4.2.2"
}, },
"dependencies": { "dependencies": {
"@types/json-schema": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
"acorn": { "acorn": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
"integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
"dev": true "dev": true
}, },
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
},
"cacache": { "cacache": {
"version": "13.0.1", "version": "13.0.1",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz",
@@ -1363,12 +1387,59 @@
"rimraf": "^2.7.1", "rimraf": "^2.7.1",
"ssri": "^7.0.0", "ssri": "^7.0.0",
"unique-filename": "^1.1.1" "unique-filename": "^1.1.1"
},
"dependencies": {
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-collect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
"integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"p-map": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
}
} }
}, },
"find-cache-dir": { "find-cache-dir": {
"version": "3.3.0", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.0.tgz", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
"integrity": "sha512-PtXtQb7IrD8O+h6Cq1dbpJH5NzD8+9keN1zZ0YlpDzl1PwXEJEBj6u1Xa92t1Hwluoozd9TNKul5Hi2iqpsWwg==", "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"commondir": "^1.0.1", "commondir": "^1.0.1",
@@ -1386,6 +1457,12 @@
"path-exists": "^4.0.0" "path-exists": "^4.0.0"
} }
}, },
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"locate-path": { "locate-path": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -1396,9 +1473,9 @@
} }
}, },
"make-dir": { "make-dir": {
"version": "3.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dev": true, "dev": true,
"requires": { "requires": {
"semver": "^6.0.0" "semver": "^6.0.0"
@@ -1450,21 +1527,73 @@
"minipass": "^3.1.1" "minipass": "^3.1.1"
} }
}, },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"terser-webpack-plugin": { "terser-webpack-plugin": {
"version": "2.3.5", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
"integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==", "integrity": "sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"cacache": "^13.0.1", "cacache": "^13.0.1",
"find-cache-dir": "^3.2.0", "find-cache-dir": "^3.3.1",
"jest-worker": "^25.1.0", "jest-worker": "^25.4.0",
"p-limit": "^2.2.2", "p-limit": "^2.3.0",
"schema-utils": "^2.6.4", "schema-utils": "^2.6.6",
"serialize-javascript": "^2.1.2", "serialize-javascript": "^4.0.0",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"terser": "^4.4.3", "terser": "^4.6.12",
"webpack-sources": "^1.4.3" "webpack-sources": "^1.4.3"
},
"dependencies": {
"jest-worker": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz",
"integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==",
"dev": true,
"requires": {
"merge-stream": "^2.0.0",
"supports-color": "^7.0.0"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"schema-utils": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
"integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.5",
"ajv": "^6.12.4",
"ajv-keywords": "^3.5.2"
}
},
"terser": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz",
"integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==",
"dev": true,
"requires": {
"commander": "^2.20.0",
"source-map": "~0.6.1",
"source-map-support": "~0.5.12"
}
}
} }
} }
} }
@@ -1785,9 +1914,9 @@
"dev": true "dev": true
}, },
"aggregate-error": { "aggregate-error": {
"version": "3.0.1", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
"integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
"dev": true, "dev": true,
"requires": { "requires": {
"clean-stack": "^2.0.0", "clean-stack": "^2.0.0",
@@ -2111,34 +2240,11 @@
"dev": true "dev": true
}, },
"axios": { "axios": {
"version": "0.19.2", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": { "requires": {
"follow-redirects": "1.5.10" "follow-redirects": "^1.10.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
} }
}, },
"babel-eslint": { "babel-eslint": {
@@ -3366,9 +3472,9 @@
"dev": true "dev": true
}, },
"copy-webpack-plugin": { "copy-webpack-plugin": {
"version": "5.1.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz",
"integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", "integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"cacache": "^12.0.3", "cacache": "^12.0.3",
@@ -3381,7 +3487,7 @@
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"p-limit": "^2.2.1", "p-limit": "^2.2.1",
"schema-utils": "^1.0.0", "schema-utils": "^1.0.0",
"serialize-javascript": "^2.1.2", "serialize-javascript": "^4.0.0",
"webpack-log": "^2.0.0" "webpack-log": "^2.0.0"
}, },
"dependencies": { "dependencies": {
@@ -4485,12 +4591,11 @@
"dev": true "dev": true
}, },
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
@@ -4503,6 +4608,12 @@
"restore-cursor": "^3.1.0" "restore-cursor": "^3.1.0"
} }
}, },
"cli-width": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz",
"integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
"dev": true
},
"color-convert": { "color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -4519,15 +4630,32 @@
"dev": true "dev": true
}, },
"eslint-scope": { "eslint-scope": {
"version": "5.0.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true, "dev": true,
"requires": { "requires": {
"esrecurse": "^4.1.0", "esrecurse": "^4.3.0",
"estraverse": "^4.1.1" "estraverse": "^4.1.1"
} }
}, },
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
"estraverse": "^5.2.0"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
}
}
},
"figures": { "figures": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -4538,18 +4666,18 @@
} }
}, },
"glob-parent": { "glob-parent": {
"version": "5.1.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
"integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
} }
}, },
"globals": { "globals": {
"version": "12.3.0", "version": "12.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
"integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"type-fest": "^0.8.1" "type-fest": "^0.8.1"
@@ -4562,9 +4690,9 @@
"dev": true "dev": true
}, },
"import-fresh": { "import-fresh": {
"version": "3.2.1", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true, "dev": true,
"requires": { "requires": {
"parent-module": "^1.0.0", "parent-module": "^1.0.0",
@@ -4572,30 +4700,30 @@
} }
}, },
"inquirer": { "inquirer": {
"version": "7.0.6", "version": "7.3.3",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.6.tgz", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
"integrity": "sha512-7SVO4h+QIdMq6XcqIqrNte3gS5MzCCKZdsq9DO4PJziBFNYzP3PGFbDjgadDb//MCahzgjCxvQ/O2wa7kx9o4w==", "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-escapes": "^4.2.1", "ansi-escapes": "^4.2.1",
"chalk": "^3.0.0", "chalk": "^4.1.0",
"cli-cursor": "^3.1.0", "cli-cursor": "^3.1.0",
"cli-width": "^2.0.0", "cli-width": "^3.0.0",
"external-editor": "^3.0.3", "external-editor": "^3.0.3",
"figures": "^3.0.0", "figures": "^3.0.0",
"lodash": "^4.17.15", "lodash": "^4.17.19",
"mute-stream": "0.0.8", "mute-stream": "0.0.8",
"run-async": "^2.4.0", "run-async": "^2.4.0",
"rxjs": "^6.5.3", "rxjs": "^6.6.0",
"string-width": "^4.1.0", "string-width": "^4.1.0",
"strip-ansi": "^6.0.0", "strip-ansi": "^6.0.0",
"through": "^2.3.6" "through": "^2.3.6"
}, },
"dependencies": { "dependencies": {
"chalk": { "chalk": {
"version": "3.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -4632,9 +4760,9 @@
"dev": true "dev": true
}, },
"onetime": { "onetime": {
"version": "5.1.0", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"mimic-fn": "^2.1.0" "mimic-fn": "^2.1.0"
@@ -4656,6 +4784,15 @@
"signal-exit": "^3.0.2" "signal-exit": "^3.0.2"
} }
}, },
"rxjs": {
"version": "6.6.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
"integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -4702,9 +4839,9 @@
} }
}, },
"supports-color": { "supports-color": {
"version": "7.1.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true, "dev": true,
"requires": { "requires": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@@ -4732,14 +4869,50 @@
} }
}, },
"eslint-plugin-vue": { "eslint-plugin-vue": {
"version": "6.2.1", "version": "7.0.0-beta.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.2.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.0.0-beta.4.tgz",
"integrity": "sha512-MiIDOotoWseIfLIfGeDzF6sDvHkVvGd2JgkvjyHtN3q4RoxdAXrAMuI3SXTOKatljgacKwpNAYShmcKZa4yZzw==", "integrity": "sha512-yb8Kki8b94a3A1sGum9TyVAkvPfKGPzWfZcXRmA2Tylt7TRi+b6mOtn1o6kM3NWatBs5gcGiCrH033DDPn0Msw==",
"dev": true, "dev": true,
"requires": { "requires": {
"eslint-utils": "^2.1.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"semver": "^5.6.0", "semver": "^7.3.2",
"vue-eslint-parser": "^7.0.0" "vue-eslint-parser": "^7.1.0"
},
"dependencies": {
"eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.1.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
} }
}, },
"eslint-scope": { "eslint-scope": {
@@ -5299,9 +5472,9 @@
} }
}, },
"flatted": { "flatted": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
"dev": true "dev": true
}, },
"flush-write-stream": { "flush-write-stream": {
@@ -5318,7 +5491,6 @@
"version": "1.11.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
"integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
"dev": true,
"requires": { "requires": {
"debug": "^3.0.0" "debug": "^3.0.0"
}, },
@@ -5327,7 +5499,6 @@
"version": "3.2.6", "version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": { "requires": {
"ms": "^2.1.1" "ms": "^2.1.1"
} }
@@ -5399,15 +5570,6 @@
"universalify": "^0.1.0" "universalify": "^0.1.0"
} }
}, },
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"fs-write-stream-atomic": { "fs-write-stream-atomic": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@@ -7161,33 +7323,6 @@
"integrity": "sha512-yV+gqbd5vaOYjqlbk16EG89xB5udgjqQF3C5FAORDg4f/IS1Yc5ERCv5e/57yBcfJYw05V5JyIXabhwb75Xxow==", "integrity": "sha512-yV+gqbd5vaOYjqlbk16EG89xB5udgjqQF3C5FAORDg4f/IS1Yc5ERCv5e/57yBcfJYw05V5JyIXabhwb75Xxow==",
"dev": true "dev": true
}, },
"jest-worker": {
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz",
"integrity": "sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg==",
"dev": true,
"requires": {
"merge-stream": "^2.0.0",
"supports-color": "^7.0.0"
},
"dependencies": {
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"jquery": { "jquery": {
"version": "3.5.1", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
@@ -7859,33 +7994,6 @@
} }
} }
}, },
"minipass-collect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
"integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-pipeline": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz",
"integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"mississippi": { "mississippi": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@@ -7951,8 +8059,7 @@
"ms": { "ms": {
"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=="
"dev": true
}, },
"multicast-dns": { "multicast-dns": {
"version": "6.2.3", "version": "6.2.3",
@@ -8051,9 +8158,9 @@
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
}, },
"node-forge": { "node-forge": {
"version": "0.9.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
"dev": true "dev": true
}, },
"node-gyp": { "node-gyp": {
@@ -8642,15 +8749,6 @@
"p-limit": "^2.0.0" "p-limit": "^2.0.0"
} }
}, },
"p-map": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
"integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
},
"p-retry": { "p-retry": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
@@ -10402,12 +10500,12 @@
"dev": true "dev": true
}, },
"selfsigned": { "selfsigned": {
"version": "1.10.7", "version": "1.10.8",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
"dev": true, "dev": true,
"requires": { "requires": {
"node-forge": "0.9.0" "node-forge": "^0.10.0"
} }
}, },
"semver": { "semver": {
@@ -10469,10 +10567,13 @@
} }
}, },
"serialize-javascript": { "serialize-javascript": {
"version": "2.1.2", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
"dev": true "dev": true,
"requires": {
"randombytes": "^2.1.0"
}
}, },
"serve-index": { "serve-index": {
"version": "1.9.1", "version": "1.9.1",
@@ -11201,9 +11302,9 @@
"dev": true "dev": true
}, },
"strip-json-comments": { "strip-json-comments": {
"version": "3.0.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true "dev": true
}, },
"stylehacks": { "stylehacks": {
@@ -11911,9 +12012,9 @@
"integrity": "sha512-Wg+ObZoYK6McHb5OOCFWvm0R7xHp0/p0G1ocx/8bO22jvA/yVY05rADbfiztwCokXBNfQuGv/XSd1ozcTFgekw==" "integrity": "sha512-Wg+ObZoYK6McHb5OOCFWvm0R7xHp0/p0G1ocx/8bO22jvA/yVY05rADbfiztwCokXBNfQuGv/XSd1ozcTFgekw=="
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==",
"dev": true "dev": true
}, },
"validate-npm-package-license": { "validate-npm-package-license": {
@@ -11966,28 +12067,62 @@
"integrity": "sha512-vuEUm6wYMMrFAHFCrkzIUAy8+MgPAbBGmYXnk2M6X6O2KHbMT1wuDD2izacmsSUp6ZM02e23MJRtPRobl88VMg==" "integrity": "sha512-vuEUm6wYMMrFAHFCrkzIUAy8+MgPAbBGmYXnk2M6X6O2KHbMT1wuDD2izacmsSUp6ZM02e23MJRtPRobl88VMg=="
}, },
"vue-eslint-parser": { "vue-eslint-parser": {
"version": "7.0.0", "version": "7.3.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.3.0.tgz",
"integrity": "sha512-yR0dLxsTT7JfD2YQo9BhnQ6bUTLsZouuzt9SKRP7XNaZJV459gvlsJo4vT2nhZ/2dH9j3c53bIx9dnqU2prM9g==", "integrity": "sha512-n5PJKZbyspD0+8LnaZgpEvNCrjQx1DyDHw8JdWwoxhhC+yRip4TAvSDpXGf9SWX6b0umeB5aR61gwUo6NVvFxw==",
"dev": true, "dev": true,
"requires": { "requires": {
"debug": "^4.1.1", "debug": "^4.1.1",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"eslint-visitor-keys": "^1.1.0", "eslint-visitor-keys": "^1.1.0",
"espree": "^6.1.2", "espree": "^6.2.1",
"esquery": "^1.0.1", "esquery": "^1.0.1",
"lodash": "^4.17.15" "lodash": "^4.17.15"
}, },
"dependencies": { "dependencies": {
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"eslint-scope": { "eslint-scope": {
"version": "5.0.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true, "dev": true,
"requires": { "requires": {
"esrecurse": "^4.1.0", "esrecurse": "^4.3.0",
"estraverse": "^4.1.1" "estraverse": "^4.1.1"
} }
},
"espree": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
"integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
"dev": true,
"requires": {
"acorn": "^7.1.1",
"acorn-jsx": "^5.2.0",
"eslint-visitor-keys": "^1.1.0"
}
},
"esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"requires": {
"estraverse": "^5.2.0"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
}
}
} }
} }
}, },
@@ -12033,6 +12168,21 @@
"vue": "^2.0.0" "vue": "^2.0.0"
} }
}, },
"vue-meta": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/vue-meta/-/vue-meta-2.4.0.tgz",
"integrity": "sha512-XEeZUmlVeODclAjCNpWDnjgw+t3WA6gdzs6ENoIAgwO1J1d5p1tezDhtteLUFwcaQaTtayRrsx7GL6oXp/m2Jw==",
"requires": {
"deepmerge": "^4.2.2"
},
"dependencies": {
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
}
}
},
"vue-router": { "vue-router": {
"version": "3.1.6", "version": "3.1.6",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.6.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.6.tgz",

View File

@@ -22,10 +22,12 @@
"url": "git+https://github.com/AlexisNP/spellsaurus.git" "url": "git+https://github.com/AlexisNP/spellsaurus.git"
}, },
"scripts": { "scripts": {
"serve": "vue-cli-service serve" "lint": "vue-cli-service lint",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
}, },
"dependencies": { "dependencies": {
"axios": "^0.19.2", "axios": "^0.21.1",
"bootstrap": "^4.5.0", "bootstrap": "^4.5.0",
"bootstrap-vue": "^2.15.0", "bootstrap-vue": "^2.15.0",
"core-js": "^3.6.4", "core-js": "^3.6.4",
@@ -35,6 +37,7 @@
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-cookies": "^1.7.0", "vue-cookies": "^1.7.0",
"vue-masonry": "^0.11.8", "vue-masonry": "^0.11.8",
"vue-meta": "^2.4.0",
"vuex": "^3.5.1" "vuex": "^3.5.1"
}, },
"devDependencies": { "devDependencies": {
@@ -42,27 +45,13 @@
"@vue/cli-plugin-eslint": "~4.2.0", "@vue/cli-plugin-eslint": "~4.2.0",
"@vue/cli-service": "~4.2.0", "@vue/cli-service": "~4.2.0",
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"eslint": "^6.7.2", "eslint": "^6.8.0",
"eslint-plugin-vue": "^6.1.2", "eslint-plugin-vue": "^7.0.0-beta.4",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"vue-router": "^3.1.6", "vue-router": "^3.1.6",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.11"
}, },
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [ "browserslist": [
"> 1%", "> 1%",
"last 2 versions" "last 2 versions"

BIN
client/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

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

View File

@@ -1,12 +1,12 @@
import api from './api' import api from './api';
const resource = "/ingredients" const resource = "/ingredients";
export default { export default {
getAll() { getAll() {
return api.get(`${resource}`) return api.get(`${resource}`);
}, },
getOne(id) { getOne(id) {
return api.get(`${resource}/${id}`) return api.get(`${resource}/${id}`);
}, },
} };

View File

@@ -1,12 +1,12 @@
import api from './api' import api from './api';
const resource = "/meta_schools" const resource = "/meta_schools";
export default { export default {
getAll() { getAll() {
return api.get(`${resource}`) return api.get(`${resource}`);
}, },
getOne(id) { getOne(id) {
return api.get(`${resource}/${id}`) return api.get(`${resource}/${id}`);
}, },
} };

View File

@@ -1,9 +1,9 @@
import spellsRepository from './spellsRepository' import spellsRepository from './spellsRepository';
import schoolsRepository from './schoolsRepository' 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' import usersRepository from './usersRepository';
// List of possible repositories // List of possible repositories
const repositories = { const repositories = {
@@ -13,9 +13,9 @@ const repositories = {
variables: variablesRepository, variables: variablesRepository,
ingredients: ingredientsRepository, ingredients: ingredientsRepository,
users: usersRepository, users: usersRepository,
} };
// Usage : RepositoryFactoryInstance.get('reponame'); // Usage : RepositoryFactoryInstance.get('reponame');
export const RepositoryFactory = { export const RepositoryFactory = {
get: name => repositories[name] get: name => repositories[name]
} };

View File

@@ -1,15 +1,15 @@
import api from './api' import api from './api';
const resource = "/schools" const resource = "/schools";
export default { export default {
getAll() { getAll() {
return api.get(`${resource}`) return api.get(`${resource}`);
}, },
getOne(id) { getOne(id) {
return api.get(`${resource}/${id}`) return api.get(`${resource}/${id}`);
}, },
getSpellsFromOne(id) { getSpellsFromOne(id) {
return api.get(`${resource}/${id}/spells`) return api.get(`${resource}/${id}/spells`);
}
} }
};

View File

@@ -1,24 +1,24 @@
import api from './api' import api from './api';
const resource = "/spells" const resource = "/spells";
export default { export default {
getAll() { getAll() {
return api.get(`${resource}`) return api.get(`${resource}`);
}, },
getPage(page) { getPage(page) {
return api.get(`${resource}/page/${page}`) return api.get(`${resource}/page/${page}`);
}, },
getOne(id) { getOne(id) {
return api.get(`${resource}/${id}`) return api.get(`${resource}/${id}`);
}, },
addOne(data) { addOne(data) {
return api.post(`${resource}`, data) return api.post(`${resource}`, data);
}, },
updateOne(id, data) { updateOne(id, data) {
return api.put(`${resource}/${id}`, data) return api.put(`${resource}/${id}`, data);
}, },
deleteOne(id) { deleteOne(id) {
return api.delete(`${resource}/${id}`) return api.delete(`${resource}/${id}`);
}
} }
};

View File

@@ -3,6 +3,12 @@ import api from './api'
const resource = "/users" const resource = "/users"
export default { export default {
getOneFromUUID(uuid) {
return api.get(`${resource}/${uuid}`,)
},
checkEmailAvailable(mail) {
return api.get(`${resource}/available/${mail}`)
},
login(data) { login(data) {
return api.post(`${resource}/login`, data) return api.post(`${resource}/login`, data)
}, },

View File

@@ -1,12 +1,12 @@
import api from './api' import api from './api';
const resource = "/variables" const resource = "/variables";
export default { export default {
getAll() { getAll() {
return api.get(`${resource}`) return api.get(`${resource}`);
}, },
getOne(id) { getOne(id) {
return api.get(`${resource}/${id}`) return api.get(`${resource}/${id}`);
}, },
} };

View File

@@ -9,8 +9,11 @@
<script> <script>
export default { export default {
name: 'app' name: 'App',
metaInfo: {
title: 'Auracle',
} }
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -54,6 +54,11 @@ hr {
font-weight: $heavy; font-weight: $heavy;
} }
// UTILITIES
.prewrap {
white-space: pre-wrap;
}
// LAYOUTS // LAYOUTS
.fullpage { .fullpage {
min-height: calc(100vh - 56px); min-height: calc(100vh - 56px);

View File

@@ -1,24 +1,24 @@
<template> <template>
<div class="lds-roller"> <div class="lds-roller">
<div></div> <div />
<div></div> <div />
<div></div> <div />
<div></div> <div />
<div></div> <div />
<div></div> <div />
<div></div> <div />
<div></div> <div />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'loader', name: 'Loader',
data() { data() {
return { return {
};
} }
} };
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -96,9 +96,9 @@ export default {
left: 12px; left: 12px;
} }
} }
}
}
}
}
@keyframes lds-roller { @keyframes lds-roller {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);

View File

@@ -1,29 +1,79 @@
<template> <template>
<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-dark"> <nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-dark">
<router-link :to="'/'" class="navbar-brand font-display font-weight-700">Auracle</router-link> <router-link
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation"> :to="'/'"
<span class="navbar-toggler-icon"></span> class="navbar-brand font-display font-weight-700"
>
Auracle
</router-link>
<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" />
</button> </button>
<div class="collapse navbar-collapse" id="navbar"> <div
<ul class="navbar-nav mr-auto" v-if="links.length != 0"> id="navbar"
<li class="nav-item" v-for="(link, index) in links" :key="index"> class="collapse navbar-collapse"
<router-link :to="link.url" class="nav-link">{{ link.text }}</router-link> >
<ul
v-if="links.length != 0"
class="navbar-nav mr-auto"
>
<li
v-for="(link, index) in links"
:key="index"
class="nav-item"
>
<router-link
:to="link.url"
class="nav-link"
>
{{ link.text }}
</router-link>
</li> </li>
</ul> </ul>
<div v-if="user" class="navbar-nav"> <div
<router-link :to="'/profil'" class="nav-link">{{ user.name }}</router-link> v-if="user"
<div @click="logoutUser()" class="nav-link">Deconnexion</div> class="navbar-nav"
>
<router-link
:to="'/profil'"
class="nav-link"
>
{{ user.name }}
</router-link>
<div
class="nav-link"
@click="logoutUser()"
>
Deconnexion
</div> </div>
<div v-else class="navbar-nav"> </div>
<router-link :to="'/connexion'" class="nav-link">Connexion</router-link> <div
v-else
class="navbar-nav"
>
<router-link
:to="'/connexion'"
class="nav-link"
>
Connexion
</router-link>
</div> </div>
</div> </div>
</nav> </nav>
</template> </template>
<script> <script>
export default { export default {
name: 'navbar', name: 'Navbar',
data() { data() {
return { return {
links: [ links: [
@@ -40,21 +90,21 @@
url: '/ages', url: '/ages',
} }
] ]
} };
}, },
computed: { computed: {
user() { user() {
return this.$store.state.user return this.$store.getters.getUserProfile;
} }
}, },
methods: { methods: {
logoutUser() { logoutUser() {
this.$cookies.remove('U_') this.$store.dispatch('user_logout');
this.$store.commit('logout') this.$router.push('/');
this.$router.push('/')
}
} }
} }
};
</script> </script>
<style lang="scss"></style> <style lang="scss"></style>

View File

@@ -2,86 +2,218 @@
<b-modal <b-modal
ref="add_spell_modal" ref="add_spell_modal"
size="lg" size="lg"
modal-class="b-modal"> modal-class="b-modal"
>
<template v-slot:modal-header="{ close }"> <template #modal-header="{ close }">
<div class="h1 modal-title font-display font-weight-bold" id="spell_show_edit_modal"> <div
id="spell_show_edit_modal"
class="h1 modal-title font-display font-weight-bold"
>
<div class="line-height-100"> <div class="line-height-100">
<span v-if="!spell.name">Nouveau sort</span> <span v-if="!spell.name">Nouveau sort</span>
<span v-if="spell.name">{{ spell.name }}</span> <span v-if="spell.name">{{ spell.name }}</span>
</div> </div>
</div> </div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" @click="close()"> <button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
@click="close()"
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</template> </template>
<template v-slot:default> <template #default>
<form id="add-spell" @submit="addSpell"> <form
id="add-spell"
@submit="addSpell"
>
<div class="form-group"> <div class="form-group">
<label for="spell_name" class="font-weight-bold col-form-label">Nom&nbsp;:</label> <label
<input type="text" class="form-control" name="spell_name" id="spell_name" placeholder="(256 caractères max.)" v-model="spell.name"> for="spell_name"
class="font-weight-bold col-form-label"
>Nom&nbsp;:</label>
<input
id="spell_name"
v-model="spell.name"
type="text"
class="form-control"
name="spell_name"
placeholder="(256 caractères max.)"
>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spell_description" class="font-weight-bold col-form-label">Description&nbsp;:</label> <label
<textarea class="form-control" name="spell_description" id="spell_description" placeholder="(2048 caractères max.)" v-model="spell.description"></textarea> for="spell_description"
class="font-weight-bold col-form-label"
>Description&nbsp;:</label>
<textarea
id="spell_description"
v-model="spell.description"
class="form-control"
name="spell_description"
placeholder="(2048 caractères max.)"
/>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input type="checkbox" class="form-check-input" id="spell_ritual" name="spell_ritual" v-model="spell.is_ritual"> <input
<label for="spell_ritual" class="font-weight-bold col-form-label">Rituel ?&nbsp;</label> id="spell_ritual"
v-model="spell.is_ritual"
type="checkbox"
class="form-check-input"
name="spell_ritual"
>
<label
for="spell_ritual"
class="font-weight-bold col-form-label"
>Rituel ?&nbsp;</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spell_level" class="font-weight-bold col-form-label">Niveau&nbsp;:</label> <label
<input type="number" class="form-control" name="spell_level" id="spell_level" min="0" max="100" step="1" placeholder="(Nombre entier de 0 à 100)" v-model="spell.level"> for="spell_level"
class="font-weight-bold col-form-label"
>Niveau&nbsp;:</label>
<input
id="spell_level"
v-model="spell.level"
type="number"
class="form-control"
name="spell_level"
min="0"
max="100"
step="1"
placeholder="(Nombre entier de 0 à 100)"
>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spell_schools" class="font-weight-bold col-form-label">École(s)&nbsp;:</label> <label
<select class="form-control" id="spell_schools" name="spell_schools" multiple v-model="spell.schools"> for="spell_schools"
<option v-for="(school, index) in all_schools" :key="index" :value="'school_' + school.id">{{school.name}}</option> class="font-weight-bold col-form-label"
>École(s)&nbsp;:</label>
<select
id="spell_schools"
v-model="spell.schools"
class="form-control"
name="spell_schools"
multiple
>
<option
v-for="(school, index) in all_schools"
:key="index"
:value="'school_' + school.id"
>
{{ school.name }}
</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spell_charge" class="font-weight-bold col-form-label">Charge&nbsp;:</label> <label
<input type="number" class="form-control" name="spell_charge" id="spell_charge" min="0" max="100" step="1" placeholder="(Nombre entier de 0 à 100)" v-model="spell.charge"> for="spell_charge"
class="font-weight-bold col-form-label"
>Charge&nbsp;:</label>
<input
id="spell_charge"
v-model="spell.charge"
type="number"
class="form-control"
name="spell_charge"
min="0"
max="100"
step="1"
placeholder="(Nombre entier de 0 à 100)"
>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spell_ingredients" class="font-weight-bold col-form-label">Ingrédient(s)&nbsp;:</label> <label
<select class="form-control" id="spell_ingredients" name="spell_ingredients" multiple v-model="spell.ingredients"> for="spell_ingredients"
<option v-for="(ingredient,index) in all_ingredients" :key="index" :value="'ingredient_' + ingredient.id">{{ingredient.name}}</option> class="font-weight-bold col-form-label"
>Ingrédient(s)&nbsp;:</label>
<select
id="spell_ingredients"
v-model="spell.ingredients"
class="form-control"
name="spell_ingredients"
multiple
>
<option
v-for="(ingredient,index) in all_ingredients"
:key="index"
:value="'ingredient_' + ingredient.id"
>
{{ ingredient.name }}
</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spell_cost" class="font-weight-bold col-form-label">Coût&nbsp;:</label> <label
<input type="text" class="form-control" name="spell_cost" id="spell_cost" placeholder="(32 caractères max.)" v-model="spell.cost"> for="spell_cost"
class="font-weight-bold col-form-label"
>Coût&nbsp;:</label>
<input
id="spell_cost"
v-model="spell.cost"
type="text"
class="form-control"
name="spell_cost"
placeholder="(32 caractères max.)"
>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="spell_variables" class="font-weight-bold col-form-label">Variable(s)&nbsp;:</label> <label
<select class="form-control" id="spell_variables" name="spell_variables" multiple v-model="spell.variables"> for="spell_variables"
<option v-for="(variable,index) in all_variables" :key="index" :value="'variable_' + variable.id">{{variable.description}}</option> class="font-weight-bold col-form-label"
>Variable(s)&nbsp;:</label>
<select
id="spell_variables"
v-model="spell.variables"
class="form-control"
name="spell_variables"
multiple
>
<option
v-for="(variable,index) in all_variables"
:key="index"
:value="'variable_' + variable.id"
>
{{ variable.description }}
</option>
</select> </select>
</div> </div>
</form> </form>
</template> </template>
<template v-slot:modal-footer="{ close }"> <template #modal-footer="{ close }">
<button type="button" class="btn btn-secondary" data-dismiss="modal" @click="close()">Fermer</button> <button
<input type="submit" class="btn btn-primary" value="Enregistrer" form="add-spell"> type="button"
class="btn btn-secondary"
data-dismiss="modal"
@click="close()"
>
Fermer
</button>
<input
type="submit"
class="btn btn-primary"
value="Enregistrer"
form="add-spell"
>
</template> </template>
</b-modal> </b-modal>
</template> </template>
<script> <script>
// API // API
import { RepositoryFactory } from "~/api/repositories" import { RepositoryFactory } from "@/api/repositories";
const Spells = RepositoryFactory.get('spells') const Spells = RepositoryFactory.get('spells');
const Schools = RepositoryFactory.get('schools') const Schools = RepositoryFactory.get('schools');
const Variables = RepositoryFactory.get('variables') const Variables = RepositoryFactory.get('variables');
const Ingredients = RepositoryFactory.get('ingredients') const Ingredients = RepositoryFactory.get('ingredients');
export default { export default {
'name': 'add-spell-card', 'name': 'AddSpellCard',
data() { data() {
return { return {
spell: { spell: {
@@ -99,37 +231,43 @@ export default {
all_schools: [], all_schools: [],
all_variables: [], all_variables: [],
all_ingredients: [], all_ingredients: [],
} };
}, },
created() { created() {
// Gets all relevant info for multiple selects // Gets all relevant info for multiple selects
let fetchSchools = Schools.getAll() let fetchSchools = Schools.getAll();
let fetchVariables = Variables.getAll() let fetchVariables = Variables.getAll();
let fetchIngredients = Ingredients.getAll() let fetchIngredients = Ingredients.getAll();
Promise.all([fetchSchools, fetchVariables, fetchIngredients]) Promise.all([fetchSchools, fetchVariables, fetchIngredients])
.then(v => { .then(v => {
this.all_schools = v[0].data this.all_schools = v[0].data;
this.all_variables = v[1].data this.all_variables = v[1].data;
this.all_ingredients = v[2].data this.all_ingredients = v[2].data;
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
}) });
}, },
mounted() { mounted() {
this.$refs["add_spell_modal"].show() this.$refs["add_spell_modal"].show();
this.$root.$on('bv::modal::hide', () => { this.$root.$on('bv::modal::hide', () => {
this.$emit('cancelAdd') this.$emit('cancelAdd');
}) });
}, },
methods: { methods: {
addSpell(e) { addSpell(e) {
e.preventDefault() e.preventDefault();
let schoolsData = Object.values(this.spell.schools).map(v => { return parseInt(v.slice(7)) }) let schoolsData = Object.values(this.spell.schools).map(v => {
let variablesData = Object.values(this.spell.variables).map(v => { return parseInt(v.slice(9)) }) return parseInt(v.slice(7));
let ingredientsData = Object.values(this.spell.ingredients).map(v => { return parseInt(v.slice(11)) }) });
let variablesData = Object.values(this.spell.variables).map(v => {
return parseInt(v.slice(9));
});
let ingredientsData = Object.values(this.spell.ingredients).map(v => {
return parseInt(v.slice(11));
});
let data = { let data = {
name: this.spell.name, name: this.spell.name,
@@ -141,19 +279,19 @@ export default {
schools: schoolsData, schools: schoolsData,
variables: variablesData, variables: variablesData,
ingredients: ingredientsData, ingredients: ingredientsData,
} };
Spells.addOne(data) Spells.addOne(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();
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
}) });
}
} }
} }
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

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

View File

@@ -1,55 +1,98 @@
<template> <template>
<div <div
:class="main_school" :class="main_school"
class="col-12 col-sm-6 col-lg-4 col-xl-3 mb-4 grid-item grid-sizer"> class="col-12 col-sm-6 col-lg-4 col-xl-3 mb-4 grid-item grid-sizer"
<div class="spellcard bg-white p-4 rounded text-dark shadow" style="border-left-width:4px;border-left-style:solid;"> >
<div
<div :title="spell.name" class="h3 font-display font-weight-bold text-wrap word-break" style="line-height:100%;"> class="spellcard bg-white p-4 rounded text-dark shadow"
<router-link :to="`/sorts/${spell.id}`" class="text-decoration-none">{{spell.name}}</router-link> 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%;"
>
<router-link
:to="`/sorts/${spell.id}`"
class="text-decoration-none"
>
{{ spell.name }}
</router-link>
</div> </div>
<div> <div>
<div class="font-weight-700 text-muted d-inline-block">Niveau {{spell.level}}</div> <div class="font-weight-700 text-muted d-inline-block">
Niveau {{ spell.level }}
</div>
<span> · </span> <span> · </span>
<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>
<router-link :to="`ecoles/${school.id}`" class="text-secondary">{{school.name}}</router-link> <router-link
:to="`ecoles/${school.id}`"
class="text-secondary"
>{{ school.name }}</router-link>
</span> </span>
</div> </div>
</div> </div>
<div v-if="spell.charge!=0" class="small font-weight-bold"> <div
v-if="spell.charge!=0"
class="small font-weight-bold"
>
<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"> <div
v-if="spell.is_ritual"
class="small font-weight-bold"
>
<span>Rituel</span> <span>Rituel</span>
</div> </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"
>
<span v-if="index!=0">, </span> <span v-if="index!=0">, </span>
<span>{{ ingredient.name }}</span> <span>{{ ingredient.name }}</span>
</span> </span>
</div> </div>
<div <div
v-clipboard="spell.description"
:id="'spell_description_' + spell.id" :id="'spell_description_' + spell.id"
v-clipboard="spell.description"
v-b-tooltip.click v-b-tooltip.click
placement="bottom" placement="bottom"
title="Description copiée !" title="Description copiée !"
class="small text-muted mt-2"> class="small text-muted mt-2"
<span style="white-space: pre-wrap;">{{spell.description}}</span> >
<span class="prewrap">{{ spell.description }}</span>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<div class="font-weight-bold d-inline-block"><span>Coût </span>{{spell.cost}}</div> <div class="font-weight-bold d-inline-block">
<div v-if="spell.variables.length>0" class="small d-inline-block">, &nbsp;:</div> <span>Coût : </span>{{ spell.cost }}
<div class=small> </div>
<span v-for="(variable,index) in spell.variables" :key="index"> <div
v-if="spell.variables.length>0"
class="small d-inline-block"
>
, &nbsp;:
</div>
<div class="small">
<span
v-for="(variable,index) in spell.variables"
:key="index"
>
<span class="font-weight-bold"> <span class="font-weight-bold">
<span v-if="index!=0"><br></span> <span v-if="index!=0"><br></span>
<span>{{ String.fromCharCode(120+index) }}</span> <span>{{ String.fromCharCode(120+index) }}</span>
@@ -57,12 +100,21 @@
<span> = {{ variable.description }}</span> <span> = {{ variable.description }}</span>
</span> </span>
</div> </div>
<footer v-if="user" 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>
<a class="h5 text-danger"> <a class="h5 text-danger">
<i class="mad" @click="deleteSpell(spell)">delete</i> <i
class="mad"
@click="deleteSpell(spell)"
>delete</i>
</a> </a>
</footer> </footer>
</div> </div>
@@ -72,43 +124,44 @@
<script> <script>
// API // API
import { RepositoryFactory } from "~/api/repositories" import { RepositoryFactory } from "@/api/repositories";
const Spells = RepositoryFactory.get('spells') const Spells = RepositoryFactory.get('spells');
export default { export default {
name: 'spell-card', name: 'SpellCard',
props: { props: {
spell: Object, spell: Object,
}, },
data() { data() {
return { return {
main_school: this.spell.schools[0].name, main_school: this.spell.schools[0].name,
} };
},
created() {
this.main_school = this.main_school.toLowerCase()
}, },
computed: { computed: {
user() { user() {
return this.$store.state.user return this.$store.getters.getUserProfile;
} }
}, },
created() {
this.main_school = this.main_school.toLowerCase();
},
methods: { methods: {
editSpell(spell) { editSpell(spell) {
this.$emit('editSpell', spell) this.$emit('editSpell', spell);
}, },
deleteSpell(spell) { deleteSpell(spell) {
Spells.deleteOne(this.spell.id) Spells.deleteOne(this.spell.id)
.then(() => { .then(() => {
this.$emit('deleteSpell', spell) this.$emit('deleteSpell', spell);
}) });
} }
}, },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.spell-card { .spell-card {
footer { footer {
a { a {
@@ -147,4 +200,5 @@ export default {
@include colorschool(somamancie,#976c67); @include colorschool(somamancie,#976c67);
@include colorschool(antimancie,#ad95c1); @include colorschool(antimancie,#ad95c1);
} }
</style> </style>

View File

@@ -1,31 +1,88 @@
<template> <template>
<div class="spell-list-wrapper"> <div class="spell-list-wrapper">
<div class="mb-4"> <div class="mb-4">
<form> <form>
<div class="form-group mb-2"> <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"> <input
id="search_terms"
v-model="search_text"
type="text"
class="form-control"
name="search_terms"
placeholder="Rechercher l'arcane"
>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<div class="form-check form-check-inline"> <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"> <input
<label class="form-check-label" for="search_fields_name">Nom</label> id="search_fields_name"
class="form-check-input"
type="radio"
name="search_term"
value="search_fields_name"
>
<label
class="form-check-label"
for="search_fields_name"
>Nom</label>
</div> </div>
<div class="form-check form-check-inline"> <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> <input
<label class="form-check-label" for="search_fields_description">Description</label> id="search_fields_description"
class="form-check-input"
type="radio"
name="search_term"
value="search_fields_description"
checked
>
<label
class="form-check-label"
for="search_fields_description"
>Description</label>
</div> </div>
<div class="form-check form-check-inline"> <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> <input
<label class="form-check-label" for="search_fields_schools">École(s)</label> id="search_fields_schools"
class="form-check-input"
type="radio"
name="search_term"
value="search_fields_schools"
disabled
>
<label
class="form-check-label"
for="search_fields_schools"
>
École(s)
</label>
</div> </div>
<div class="form-check form-check-inline"> <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> <input
<label class="form-check-label" for="search_fields_ingredients">Ingrédients</label> id="search_fields_ingredients"
class="form-check-input"
type="radio"
name="search_term"
value="search_fields_ingredients"
disabled
>
<label
class="form-check-label"
for="search_fields_ingredients"
>Ingrédients</label>
</div> </div>
<div class="form-check form-check-inline"> <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> <input
<label class="form-check-label" for="search_fields_variables">Variables</label> id="search_fields_variables"
class="form-check-input"
type="radio"
name="search_term"
value="search_fields_variables"
disabled
>
<label
class="form-check-label"
for="search_fields_variables"
>Variables</label>
</div> </div>
</div> </div>
</form> </form>
@@ -33,46 +90,52 @@
<button <button
v-if="user" v-if="user"
@click="showAdd"
type="button" type="button"
class="btn font-display font-weight-bold btn-lg btn-outline-dark btn-block shadow-sm mb-4"> class="btn font-display font-weight-bold btn-lg btn-outline-dark btn-block shadow-sm mb-4"
@click="showAdd"
>
<i class="mad">add</i> <i class="mad">add</i>
<span>Ajouter un sort</span> <span>Ajouter un sort</span>
</button> </button>
<div <div
v-if="filteredSpells.length > 0" v-if="filteredSpells.length > 0"
class="spell-list-wrapper"> class="spell-list-wrapper"
>
<div <div
class="row spells-list"
v-masonry v-masonry
class="row spells-list"
transition-duration="1s" transition-duration="1s"
item-selector=".spell-card"> item-selector=".spell-card"
>
<spell-card <spell-card
class="spell-card"
v-masonry-tile
v-for="(spell) in filteredSpells" v-for="(spell) in filteredSpells"
:key="spell.id" :key="spell.id"
v-masonry-tile
class="spell-card"
:spell="spell" :spell="spell"
@editSpell="editSpell" @editSpell="editSpell"
@deleteSpell="deleteSpell" /> @deleteSpell="deleteSpell"
/>
</div> </div>
<edit-spell-card <edit-spell-card
v-if="Object.keys(current_edit_spell).length > 0" v-if="Object.keys(current_edit_spell).length > 0"
:spell="current_edit_spell" :spell="current_edit_spell"
@editSpell="editSpell" @editSpell="editSpell"
@updateSpell="updateSpell"/> @updateSpell="updateSpell"
/>
<add-spell-card <add-spell-card
v-if="adding_spell" v-if="adding_spell"
@cancelAdd="cancelAdd" @cancelAdd="cancelAdd"
@addSpell="addSpell"/> @addSpell="addSpell"
/>
</div> </div>
<div <div
v-else v-else
class="loader-wrapper"> class="loader-wrapper"
>
<loader /> <loader />
</div> </div>
</div> </div>
@@ -80,24 +143,27 @@
<script> <script>
// Components // Components
import SpellCard from "./spell-card" import SpellCard from "./spell-card";
import EditSpellCard from "./edit-spell-card" import EditSpellCard from "./edit-spell-card";
import AddSpellCard from "./add-spell-card" import AddSpellCard from "./add-spell-card";
// API // API
import { RepositoryFactory } from "~/api/repositories" import { RepositoryFactory } from "@/api/repositories";
const Spells = RepositoryFactory.get('spells') const Spells = RepositoryFactory.get('spells');
const Schools = RepositoryFactory.get('schools') const Schools = RepositoryFactory.get('schools');
export default { export default {
name: 'spellslist', name: 'Spellslist',
components: { components: {
'spell-card': SpellCard, 'spell-card': SpellCard,
'edit-spell-card': EditSpellCard, 'edit-spell-card': EditSpellCard,
'add-spell-card': AddSpellCard, 'add-spell-card': AddSpellCard,
}, },
props: { props: {
school_id: String, schoolId: {
type: Number,
default: 1
},
}, },
data() { data() {
return { return {
@@ -107,118 +173,118 @@ export default {
currentIndex: 1, currentIndex: 1,
adding_spell: false, adding_spell: false,
search_text: "", search_text: "",
} };
}, },
computed: { computed: {
filteredSpells() { filteredSpells() {
return this.spells return this.spells;
}, },
user() { user() {
return this.$store.state.user return this.$store.state.user;
} }
}, },
beforeMount() { beforeMount() {
this.spells = this.filteredSpells this.spells = this.filteredSpells;
if (!this.school_id) { if (!this.school_id) {
this.getInitialSpells() this.getInitialSpells();
} else { } else {
this.getInitialSchoolSpells() this.getInitialSchoolSpells();
} }
}, },
mounted() { mounted() {
if (!this.school_id) { if (!this.school_id) {
this.scroll() this.scroll();
} }
}, },
methods: { methods: {
getInitialSpells() { getInitialSpells() {
Spells.getPage(this.currentIndex) Spells.getPage(this.currentIndex)
.then(v => { .then(v => {
this.loading = true this.loading = true;
let spells = this.filteredSpells let spells = this.filteredSpells;
spells.push(v.data) spells.push(v.data);
this.spells = spells[0] this.spells = spells[0];
}) })
.then(() => { .then(() => {
this.currentIndex++ this.currentIndex++;
this.loading = false this.loading = false;
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
}) });
}, },
getInitialSchoolSpells() { getInitialSchoolSpells() {
Schools.getSpellsFromOne(this.school_id) Schools.getSpellsFromOne(this.school_id)
.then(v => { .then(v => {
this.loading = true this.loading = true;
let spells = this.filteredSpells let spells = this.filteredSpells;
spells.push(v.data.spells) spells.push(v.data.spells);
this.spells = spells[0] this.spells = spells[0];
}) })
.then(() => { .then(() => {
this.currentIndex++ this.currentIndex++;
this.loading = false this.loading = false;
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
}) });
}, },
scroll() { scroll() {
window.onscroll = () => { window.onscroll = () => {
if (((window.innerHeight + window.scrollY) - document.body.offsetHeight) >= -1) { if (((window.innerHeight + window.scrollY) - document.body.offsetHeight) >= -1) {
Spells.getPage(this.currentIndex) Spells.getPage(this.currentIndex)
.then(v => { .then(v => {
this.loading = true this.loading = true;
let spells = this.filteredSpells let spells = this.filteredSpells;
for (let i = 0; i < v.data.length; i++) { for (let i = 0; i < v.data.length; i++) {
const element = v.data[i]; const element = v.data[i];
spells.push(element) spells.push(element);
} }
}) })
.then(() => { .then(() => {
this.currentIndex++ this.currentIndex++;
this.loading = false this.loading = false;
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
}) });
}
} }
};
}, },
editSpell(e) { editSpell(e) {
this.current_edit_spell = e; this.current_edit_spell = e;
}, },
showAdd() { showAdd() {
this.adding_spell = true this.adding_spell = true;
}, },
cancelAdd() { cancelAdd() {
this.adding_spell = false this.adding_spell = false;
}, },
// Receives events to update the data // Receives events to update the data
addSpell(e) { addSpell(e) {
Spells.getOne(e.id) Spells.getOne(e.id)
.then(v => { .then(v => {
let spells = this.filteredSpells let spells = this.filteredSpells;
spells.unshift(v.data) spells.unshift(v.data);
}) })
.then(() => { .then(() => {
this.adding_spell = false this.adding_spell = false;
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err);
}) });
}, },
updateSpell(e) { updateSpell(e) {
let oldSpell = this.spells.find(x => x.id === e.id) let oldSpell = this.spells.find(x => x.id === e.id);
this.spells.splice(this.spells.indexOf(oldSpell), 1, e) this.spells.splice(this.spells.indexOf(oldSpell), 1, e);
this.current_edit_spell = {} this.current_edit_spell = {};
}, },
deleteSpell(e) { deleteSpell(e) {
this.spells.splice(this.spells.indexOf(e), 1) this.spells.splice(this.spells.indexOf(e), 1);
this.current_edit_spell = {} this.current_edit_spell = {};
}
} }
} }
};
</script> </script>

View File

@@ -1,18 +1,22 @@
<template> <template>
<ul class="timeline"> <ul class="timeline">
<li class="timeline-item period first"> <li class="timeline-item period first">
<div class="timeline-info"></div> <div class="timeline-info" />
<div class="timeline-content"> <div class="timeline-content">
<h2 class="timeline-title">LE RENOUVEAU</h2> <h2 class="timeline-title">
LE RENOUVEAU
</h2>
</div> </div>
</li> </li>
<li class="timeline-item"> <li class="timeline-item">
<div class="timeline-info"> <div class="timeline-info">
<span>March 12, 2016</span> <span>March 12, 2016</span>
</div> </div>
<div class="timeline-marker"></div> <div class="timeline-marker" />
<div class="timeline-content"> <div class="timeline-content">
<h3 class="timeline-title">Event Title</h3> <h3 class="timeline-title">
Event Title
</h3>
<p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque.</p> <p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque.</p>
</div> </div>
</li> </li>
@@ -20,26 +24,32 @@
<div class="timeline-info"> <div class="timeline-info">
<span>March 23, 2016</span> <span>March 23, 2016</span>
</div> </div>
<div class="timeline-marker"></div> <div class="timeline-marker" />
<div class="timeline-content"> <div class="timeline-content">
<h3 class="timeline-title">Event Title</h3> <h3 class="timeline-title">
Event Title
</h3>
<p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque. </p> <p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque. </p>
</div> </div>
</li> </li>
<li class="timeline-item period"> <li class="timeline-item period">
<div class="timeline-info"></div> <div class="timeline-info" />
<div class="timeline-marker"></div> <div class="timeline-marker" />
<div class="timeline-content"> <div class="timeline-content">
<h2 class="timeline-title">April 2016</h2> <h2 class="timeline-title">
April 2016
</h2>
</div> </div>
</li> </li>
<li class="timeline-item"> <li class="timeline-item">
<div class="timeline-info"> <div class="timeline-info">
<span>April 02, 2016</span> <span>April 02, 2016</span>
</div> </div>
<div class="timeline-marker"></div> <div class="timeline-marker" />
<div class="timeline-content"> <div class="timeline-content">
<h3 class="timeline-title">Event Title</h3> <h3 class="timeline-title">
Event Title
</h3>
<p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque. </p> <p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque. </p>
</div> </div>
</li> </li>
@@ -47,20 +57,21 @@
<div class="timeline-info"> <div class="timeline-info">
<span>April 28, 2016</span> <span>April 28, 2016</span>
</div> </div>
<div class="timeline-marker"></div> <div class="timeline-marker" />
<div class="timeline-content"> <div class="timeline-content">
<h3 class="timeline-title">Event Title</h3> <h3 class="timeline-title">
Event Title
</h3>
<p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque. </p> <p>Nullam vel sem. Nullam vel sem. Integer ante arcu, accumsan a, consectetuer eget, posuere ut, mauris. Donec orci lectus, aliquam ut, faucibus non, euismod id, nulla. Donec vitae sapien ut libero venenatis faucibus. ullam dictum felis eu pede mollis pretium. Pellentesque ut neque. </p>
</div> </div>
</li> </li>
</ul> </ul>
</template> </template>
<script> <script>
export default { export default {
name: 'timeline', name: 'Timeline',
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -0,0 +1,17 @@
<template>
<form>
<div>Update profile for {{ user.name }}</div>
</form>
</template>
<script>
export default {
name: 'UpdateForm',
props: {
user: {
type: Object,
default: null,
},
}
};
</script>

View File

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

View File

@@ -1,96 +1,76 @@
// 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' import VueMeta from 'vue-meta';
import Globals from './global-components.js' Vue.use(VueMeta, {
Globals.forEach(component => { // optional pluginOptions
Vue.component(component.name, component) refreshOnceOnNavigation: true
}); });
// Environment // Environment
require('dotenv').config() import dotenv from 'dotenv';
dotenv.config();
// Store
import store from './store';
// Global components
import Globals from './global-components.js';
Globals.forEach(component => {
Vue.component(component.name, component);
});
// Cookies // Cookies
import VueCookies from 'vue-cookies' import VueCookies from 'vue-cookies';
Vue.use(VueCookies) 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;
// Styles // Styles
// Fonts // Fonts
import './assets/scss/_fonts.scss' import './assets/scss/_fonts.scss';
import './assets/scss/_global.scss' import './assets/scss/_global.scss';
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 // Plugins
import { BootstrapVue } from 'bootstrap-vue' import { BootstrapVue } from 'bootstrap-vue';
Vue.use(BootstrapVue) Vue.use(BootstrapVue);
import { VueMasonryPlugin } from 'vue-masonry' // Masonry (will probably get removed in the very very near future lmao)
Vue.use(VueMasonryPlugin) import { VueMasonryPlugin } from 'vue-masonry';
Vue.use(VueMasonryPlugin);
import clipboard from 'v-clipboard' // Clipboard (might find a better one or code it myself idk)
Vue.use(clipboard) import clipboard from 'v-clipboard';
Vue.use(clipboard);
// FUNCTIONS // FUNCTIONS
var filter = function(text, length, clamp){ // Let's be honest i will 1000% refactor this.
let filter = (text, length, clamp) => {
clamp = clamp || '...'; clamp = clamp || '...';
var node = document.createElement('div'); let node = document.createElement('div');
node.innerHTML = text; node.innerHTML = text;
var content = node.textContent; let 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);
// Router // Router
import router from './routes' import router from './routes';
Vue.use(VueRouter) Vue.use(VueRouter);
// Store
Vue.use(Vuex)
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 // Mount Vue
const app = new Vue({ const app = new Vue({
render: h => h(App), render: h => h(App),
router, router,
store: store store: store
}) });
app.$mount('#srs') app.$mount('#srs');

View File

@@ -2,10 +2,16 @@
<div class="container-fluid"> <div class="container-fluid">
<section class="d-flex justify-content-center align-items-center"> <section class="d-flex justify-content-center align-items-center">
<main class="px-4"> <main class="px-4">
<h1 class="title text-dark mb-4 font-display font-weight-black line-height-100">"C'est magique, ta gueule."</h1> <h1 class="title text-dark mb-4 font-display font-weight-black line-height-100">
<div class="lead font-display font-weight-bold"> N'importe quel MJ, une fois dans sa vie</div> "C'est magique, ta gueule."
</h1>
<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 font-weight-bold">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 font-weight-bold">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>
</main> </main>
</section> </section>
</div> </div>
@@ -13,8 +19,8 @@
<script> <script>
export default { export default {
name: 'index-page', name: 'IndexPage',
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,13 +1,23 @@
<template> <template>
<div class="container-fluid p-4" id="spell-container"> <div
<h1 class="display-3 font-display mb-3">Écoles</h1> id="spell-container"
class="container-fluid p-4"
>
<h1 class="display-3 font-display mb-3">
Écoles
</h1>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'schools-page' name: 'SchoolsPage',
} metaInfo: {
titleTemplate: '%s - Maîtrises'
},
};
</script> </script>
<style> <style>

View File

@@ -1,46 +1,52 @@
<template> <template>
<div class="container-fluid p-4" id="spell-container"> <div
<h1 class="display-3 font-display mb-3">{{ school.name }}</h1> id="spell-container"
class="container-fluid p-4"
>
<h1 class="display-3 font-display mb-3">
{{ school.name }}
</h1>
<p>{{ school.description }}</p> <p>{{ school.description }}</p>
<spell-list :school_id="id" /> <spell-list :school_id="id" />
</div> </div>
</template> </template>
<script> <script>
import SpellsList from "~/components/spells/spells-list"
// API // API
import { RepositoryFactory } from "~/api/repositories" import { RepositoryFactory } from "@/api/repositories";
const Schools = RepositoryFactory.get('schools') const Schools = RepositoryFactory.get('schools');
import SpellsList from "@/components/spells/spells-list";
export default { export default {
name: 'single-school-page', name: 'SingleSchoolPage',
metaInfo() {
return {
titleTemplate: `%s - ${this.school.name}`,
};
},
components: { components: {
'spell-list': SpellsList, 'spell-list': SpellsList,
}, },
data() { data() {
return { return {
loading: false, id: this.$route.params.id,
school: {}, school: {},
id: this.$route.params.id errors: {
loading: "",
} }
};
}, },
created() { created() {
this.computeSchool() Schools.getOne(this.id)
.then(v => {
this.school = v.data;
})
.catch(err => {
console.log(err);
});
}, },
methods: { };
async fetchSchool(id) {
const { data } = await Schools.getOne(id)
return data
},
async computeSchool() {
this.loading = true
const displaySchool = await this.fetchSchool(this.id)
this.loading = false
this.school = displaySchool
},
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -1,13 +1,48 @@
<template> <template>
<div class="container-fluid p-4" id="spell-container"> <div
<h1 class="display-3 font-display mb-3">Sortilège</h1> id="spell-container"
class="container-fluid p-4"
>
<h1 class="display-3 font-display mb-3">
{{ spell.name }}
</h1>
<p class="prewrap">
{{ spell.description }}
</p>
</div> </div>
</template> </template>
<script> <script>
export default { // API
import { RepositoryFactory } from "@/api/repositories";
const Spells = RepositoryFactory.get('spells');
export default {
name: 'SingleSpellPage',
metaInfo() {
return {
titleTemplate: this.spell.name ? `%s - ${this.spell.name}` : '%s',
};
},
data() {
return {
id: this.$route.params.id,
spell: {},
errors: {
loading: "",
} }
};
},
created() {
Spells.getOne(this.id)
.then(v => {
this.spell = v.data;
})
.catch(err => {
console.log(err);
});
},
};
</script> </script>
<style> <style>

View File

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

View File

@@ -1,19 +1,27 @@
<template> <template>
<div class="container-fluid p-4" id="spell-container"> <div
<h1 class="display-3 font-display mb-3">Chronologie</h1> id="spell-container"
class="container-fluid p-4"
>
<h1 class="display-3 font-display mb-3">
Chronologie
</h1>
<timeline /> <timeline />
</div> </div>
</template> </template>
<script> <script>
import Timeline from '~/components/timeline/timeline' import Timeline from "@/components/timeline/timeline";
export default { export default {
name: 'timeline-page', name: 'TimelinePage',
metaInfo: {
titleTemplate: '%s - Âges'
},
components: { components: {
'timeline': Timeline, 'timeline': Timeline
}
} }
};
</script> </script>
<style> <style>

View File

@@ -3,7 +3,9 @@
<section class="d-flex justify-content-center align-items-center"> <section class="d-flex justify-content-center align-items-center">
<main> <main>
<div class="title font-display mb-3">Connexion</div> <div class="title font-display mb-3">Connexion</div>
<form @submit="logUser"> <form @submit="logUser">
<div class="form-group"> <div class="form-group">
<label for="email">Addresse mail</label> <label for="email">Addresse mail</label>
<input <input
@@ -11,10 +13,24 @@
type="email" type="email"
id="email" id="email"
class="form-control" class="form-control"
:class="{ 'is-invalid': errors.email || errors.login }"
placeholder="john.doe@gmail.com" placeholder="john.doe@gmail.com"
autocomplete="email"> 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>
<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="form-text text-danger"
>
{{ errors.email }}
</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Mot de passe</label> <label for="password">Mot de passe</label>
<input <input
@@ -22,11 +38,44 @@
type="password" type="password"
id="password" id="password"
class="form-control" class="form-control"
:class="{ 'is-invalid': errors.password || errors.login }"
autocomplete="current-password"> autocomplete="current-password">
<small
v-if="!errors.password"
class="form-text text-muted"
>
Nous vous conseillons d'utiliser un mot de passe unique pour cette application seulement.
</small>
<small
v-if="errors.password"
class="form-text text-danger"
>
{{ errors.password }}
</small>
</div> </div>
<div class="form-check">
<input
type="checkbox"
v-model="remember_me"
id="remember-me"
class="form-check-input">
<label
class="form-check-label"
for="remember-me"
>
Rester connecté
</label>
</div>
<div class="form-group">
<small v-if="errors.login" class="text-danger">{{ errors.login }}</small>
</div>
<button type="submit" class="btn btn-primary">Connexion</button> <button type="submit" class="btn btn-primary">Connexion</button>
<router-link :to="'/inscription'" class="btn btn-dark">Inscription</router-link> <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> <small class="form-text text-muted">Mot de passe oublié ? <a href="#">Vous pouvez le changer ici !</a></small>
</form> </form>
</main> </main>
</section> </section>
@@ -34,45 +83,80 @@
</template> </template>
<script> <script>
// API
import { RepositoryFactory } from "~/api/repositories"
const Users = RepositoryFactory.get('users')
export default { export default {
name: 'login-page', name: 'login-page',
metaInfo: {
titleTemplate: '%s - Connexion'
},
data() { data() {
return { return {
email: "", email: "",
password: "", password: "",
remember_me: false,
submitted: false, submitted: false,
errors: { errors: {
login: "" email: "",
password: "",
login: "",
} }
} }
}, },
methods: { methods: {
async logUser(e) { async logUser(e) {
e.preventDefault() e.preventDefault();
Users.login({ // Resets old errors if any
for (const o in this.errors) {
this.errors[o] = ""
}
this.submitted = true;
let userInput = {
"user": {
"mail": this.email, "mail": this.email,
"password": this.password "password": this.password,
}) },
.then(v => { "remember_me": this.remember_me
let user = v.data.user };
this.$cookies.set("U_", user, 60 * 60 * 24 * 30)
this.$store.commit('loginSucceed', user) // Check is all inputs are set
if (!userInput.user.mail) {
this.errors.email = "Vous devez renseigner une addresse mail."
}
if (!userInput.user.password) {
this.errors.password = "Vous devez renseigner un mot de passe."
}
// Stops the method if any errors exist
for (const o in this.errors) {
if (this.errors[o] != "") {
this.submitted = false;
}
}
// If no errors are present, submits the form
if (this.submitted) {
this.$store.dispatch('user_login', userInput)
.then(() => {
this.$router.push('/profil') this.$router.push('/profil')
}) })
.catch(() => { .catch(err => {
this.$store.commit('loginFailed') if (err.status != 500) {
}) this.errors.email = err.data.error;
} else {
this.errors.login = err.data.error;
} }
});
}
},
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
section { section {
min-height: calc(100vh - 56px); min-height: calc(100vh - 56px);
main { main {
@@ -84,9 +168,15 @@ section {
} }
} }
} }
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
section {
main {
.title { .title {
font-size: 3.5rem; font-size: 3.5rem;
} }
} }
}
}
</style> </style>

View File

@@ -1,24 +1,103 @@
<template> <template>
<div class="container-fluid p-4" id="spell-container"> <div
<div class="display-3 font-display mb-3"> v-if="user"
id="spell-container"
class="container-fluid p-4"
>
<h2 class="display-3 font-display mb-3">
<span class="username">{{ user.name }}</span> <span class="username">{{ user.name }}</span>
<span v-if="user.verified" class="verified"><i class="mad">check_circle</i></span> </h2>
<span>Membre depuis le {{ registered_date }}</span>
<ul
id="tabs-tab"
class="nav nav-tabs mt-3 mb-3"
role="tablist"
>
<li class="nav-item">
<a
id="tabs-home-tab"
class="nav-link active"
data-toggle="pill"
href="#tabs-home"
role="tab"
aria-controls="tabs-home"
aria-selected="true"
>Mes sorts</a>
</li>
<li class="nav-item">
<a
id="tabs-contact-tab"
class="nav-link"
data-toggle="pill"
href="#tabs-contact"
role="tab"
aria-controls="tabs-contact"
aria-selected="false"
>Paramètres</a>
</li>
</ul>
<div
id="tabs-tabContent"
class="tab-content"
>
<div
id="tabs-home"
class="tab-pane fade show active"
role="tabpanel"
aria-labelledby="tabs-home-tab"
>
...
</div>
<div
id="tabs-contact"
class="tab-pane fade"
role="tabpanel"
aria-labelledby="tabs-contact-tab"
>
<update-form :user="user" />
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import UpdateForm from '@/components/user/profile/update-form.vue';
export default { export default {
name: 'profile-page', name: 'ProfilePage',
metaInfo: {
titleTemplate: '%s - Profil'
},
components: {
UpdateForm,
},
data() {
return {
};
},
computed: { computed: {
user() { user() {
return this.$store.state.user return this.$store.getters.getUserProfile;
},
registered_date() {
let raw_date = new Date(this.user.register_date);
let date_options = {
day: 'numeric',
month: 'long',
year: 'numeric',
};
return new Intl.DateTimeFormat("fr", date_options).format(raw_date);
} }
}, },
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.title { .title {
font-size: 5rem; font-size: 5rem;
font-weight: 700; font-weight: 700;
@@ -28,4 +107,5 @@ export default {
font-size: 3.5rem; font-size: 3.5rem;
} }
} }
</style> </style>

View File

@@ -4,8 +4,9 @@
<main> <main>
<div class="title font-display mb-3">Inscription</div> <div class="title font-display mb-3">Inscription</div>
<form @submit="registerUser"> <form @submit="registerUser">
<div class="form-group"> <div class="form-group">
<label for="email">Pseudo</label> <label for="email">Nom d'utilisateur</label>
<input <input
v-model="name" v-model="name"
:class="{ :class="{
@@ -16,11 +17,23 @@
class="form-control" class="form-control"
placeholder="John Doe" placeholder="John Doe"
autocomplete="username"> autocomplete="username">
<small v-if="!errors.name" class="form-text text-muted">Vous pouvez changer votre pseudo à tout moment.</small> <small
<small v-if="errors.name" class="text-danger">{{ errors.name }}</small> v-if="!errors.name"
class="form-text text-muted"
>
Vous pouvez changer votre pseudo à tout moment.
</small>
<small
v-if="errors.name"
class="form-text text-danger"
>
{{ errors.name }}
</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Addresse mail</label> <label for="email">Adresse mail</label>
<input <input
v-model="email" v-model="email"
:class="{ :class="{
@@ -31,9 +44,21 @@
class="form-control" class="form-control"
placeholder="john.doe@gmail.com" placeholder="john.doe@gmail.com"
autocomplete="email"> 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
<small v-if="errors.email" class="text-danger">{{ errors.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="form-text text-danger"
>
{{ errors.email }}
</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Mot de passe</label> <label for="password">Mot de passe</label>
<input <input
@@ -46,6 +71,7 @@
class="form-control" class="form-control"
autocomplete="password"> autocomplete="password">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password_check">Confirmer le mot de passe</label> <label for="password_check">Confirmer le mot de passe</label>
<input <input
@@ -57,10 +83,31 @@
id="password_check" id="password_check"
class="form-control" class="form-control"
autocomplete="password-verification"> 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
<small v-if="errors.password" class="text-danger">{{ errors.password }}</small> v-if="!errors.password && !errors.password_check"
<small v-if="errors.password_check" class="text-danger">{{ errors.password_check }}</small> 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="form-text text-danger"
>
{{ errors.password }}
</small>
<small
v-if="errors.password_check"
class="form-text text-danger"
>
{{ errors.password_check }}
</small>
</div> </div>
<div class="form-group">
<small v-if="errors.register" class="text-danger">{{ errors.register }}</small>
</div>
<button type="submit" class="btn btn-dark">Inscription</button> <button type="submit" class="btn btn-dark">Inscription</button>
</form> </form>
</main> </main>
@@ -69,12 +116,11 @@
</template> </template>
<script> <script>
// API
import { RepositoryFactory } from "~/api/repositories"
const Users = RepositoryFactory.get('users')
export default { export default {
name: 'register-page', name: 'register-page',
metaInfo: {
titleTemplate: '%s - Inscription'
},
data() { data() {
return { return {
name: "", name: "",
@@ -87,55 +133,64 @@ export default {
email: "", email: "",
password: "", password: "",
password_check: "", password_check: "",
register: "",
}, },
} }
}, },
methods: { methods: {
async registerUser(e) { async registerUser(e) {
e.preventDefault() e.preventDefault();
// Resets old errors if any // Resets old errors if any
for (const o in this.errors) { for (const o in this.errors) {
this.errors[o] = "" this.errors[o] = ""
} }
this.submitted = true this.submitted = true;
if (!this.name) { let userInput = {
"user": {
"name": this.name,
"mail": this.email,
"password": this.password
}
}
// Check if all inputs are valid without sending anything
if (!userInput.user.name) {
this.errors.name = "Vous devez renseigner un pseudonyme." this.errors.name = "Vous devez renseigner un pseudonyme."
} }
if (!this.email) { if (!userInput.user.mail) {
this.errors.email = "Vous devez renseigner une addresse mail." this.errors.email = "Vous devez renseigner une addresse mail."
} }
if (!this.password) { if (!userInput.user.password) {
this.errors.password = "Vous devez renseigner un mot de passe." this.errors.password = "Vous devez renseigner un mot de passe."
} }
if ((this.password.localeCompare(this.password_check)) != 0) { if ((userInput.user.password.localeCompare(this.password_check)) != 0) {
this.errors.password_check = "Les mots de passe ne correspondent pas." this.errors.password_check = "Les mots de passe ne correspondent pas."
} }
// Stops the method if any errors exist // Stops the method if any errors exist
for (const o in this.errors) { for (const o in this.errors) {
if (this.errors[o]) { if (this.errors[o] != "") {
return true this.submitted = false;
} }
} }
Users.register({ // If no errors are present, submits the form
"name": this.name, if (this.submitted) {
"mail": this.email, this.$store.dispatch('user_register', userInput)
"password": this.password .then(() => {
}) this.$router.push('/');
.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 => { .catch(err => {
let error = err.response.data.error // Sets errors in case something wrong comes back from the API
this.errors.email = error if (err.status === 409) {
this.$store.commit('registerFail') this.errors.email = err.data.error;
}) } else {
this.errors.register = "Une erreur inconnue est survenue, rééssayez plus tard.";
}
});
}
} }
} }
} }

View File

@@ -1,20 +1,21 @@
import Vue from 'vue' import VueRouter from 'vue-router';
import VueRouter from 'vue-router'
import store from './store';
// Pages // Pages
import Index from "~/pages/index-page" import Index from "@/pages/index-page";
import Spells from "~/pages/spells/spells-page" import Spells from "@/pages/spells/spells-page";
import SpellSingle from "~/pages/spells/single-spell-page" import SpellSingle from "@/pages/spells/single-spell-page";
import Schools from "./pages/schools/schools-page" import Schools from "./pages/schools/schools-page";
import SchoolSingle from "~/pages/schools/single-school-page" import SchoolSingle from "@/pages/schools/single-school-page";
import Timeline from "./pages/timelines/timeline-page" import Timeline from "./pages/timelines/timeline-page";
import Login from "~/pages/user/login-page" import Profile from "@/pages/user/profile-page";
import Register from "~/pages/user/register-page" import Login from "@/pages/user/login-page";
import Profile from "~/pages/user/profile-page" import Register from "@/pages/user/register-page";
// Routes // Routes
const routes = [ const routes = [
@@ -30,14 +31,14 @@ const routes = [
path: '/connexion', path: '/connexion',
component: Login, component: Login,
meta: { meta: {
antiAuthGuard: true loginFilter: true
} }
}, },
{ {
path: '/inscription', path: '/inscription',
component: Register, component: Register,
meta: { meta: {
antiAuthGuard: true loginFilter: true
} }
}, },
{ {
@@ -67,31 +68,35 @@ const routes = [
component: Profile, component: Profile,
props: true, props: true,
meta: { meta: {
authGuard: true, logoutFilter: true,
} }
}, },
] ];
const router = new VueRouter({ const router = new VueRouter({
mode: 'history', mode: 'history',
routes, routes,
linkActiveClass: "", linkActiveClass: "",
linkExactActiveClass: "active", linkExactActiveClass: "active",
}) });
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.authGuard)) { if (to.matched.some(record => record.meta.loginFilter)) {
if (Vue.$cookies.get('U_') == null) { if (store.getters.getUserProfile) {
next({
path: '/',
});
}
next();
} else if (to.matched.some(record => record.meta.logoutFilter)) {
if (!store.getters.getUserProfile) {
next({ next({
path: '/connexion', path: '/connexion',
params: { nextUrl: to.fullPath }, });
})
} else {
next()
} }
} else { next();
next()
} }
}) next();
});
export default router; export default router;

19
client/src/store/index.js Normal file
View File

@@ -0,0 +1,19 @@
import Vue from 'vue';
import Vuex from 'vuex';
import VueCookies from 'vue-cookies';
Vue.use(VueCookies);
Vue.$cookies.config('30d','','');
Vue.use(Vuex);
import user from './modules/user.store';
const debug = process.env.NODE_ENV !== 'production';
export default new Vuex.Store({
modules: {
user
},
strict: debug
});

View File

@@ -0,0 +1,23 @@
const state = {
};
const getters = {
};
const actions = {
};
const mutations = {
};
export default {
namespaced: true,
state,
getters,
actions,
mutations,
};

View File

@@ -0,0 +1,91 @@
import cookie from 'vue-cookies';
// API
import { RepositoryFactory } from "@/api/repositories";
const Users = RepositoryFactory.get('users');
export const namespaced = true;
const state = {
profile: cookie.get('user_profile') || null,
};
const getters = {
getUserProfile: state => {
return state.profile;
}
};
const mutations = {
loginUser(state, user) {
state.profile = user;
},
logoutUser(state) {
state.profile = null;
},
registerUser() {
// Will contain the email call eventually
}
};
const actions = {
/**
* @param data
* An object containing :
* - user object with mail and password hash properties
* - remember_me boolean to check cookie expiration time
*/
user_login ({ commit }, data) {
return new Promise((resolve, reject) => {
Users.login(data.user)
.then(v => {
let user = v.data.user;
// Check if the use wishes to be remembered
if (data.remember_me) {
cookie.set('user_profile', user, 60 * 60 * 24 * 30); // Expires after a month
} else {
cookie.set('user_profile', user, 0); // Expires after browser session expires
}
commit('loginUser', user);
resolve(user);
})
.catch(err => {
reject(err.response);
});
});
},
user_logout ({ commit }) {
cookie.remove('user_profile');
commit('logoutUser');
},
/**
* @param data
* An object containing :
* - user object with string mail, string name, and string password
*/
user_register ({ commit }, data) {
return new Promise((resolve, reject) => {
Users.register(data.user)
.then(() => {
commit('registerUser');
resolve();
})
.catch(err => {
reject(err.response);
});
});
}
};
export default {
state,
getters,
actions,
mutations,
};

View File

@@ -1,19 +1,18 @@
const path = require('path') const path = require('path');
module.exports = { module.exports = {
lintOnSave: false,
configureWebpack: { configureWebpack: {
resolve: { resolve: {
alias: { alias: {
"~": path.resolve(__dirname, 'src/') "@": path.resolve(__dirname, 'src/')
} }
} }
}, },
css: { css: {
loaderOptions: { loaderOptions: {
scss: { scss: {
prependData: ` prependData: `@import "@/assets/scss/_variables.scss";`
@import "@/assets/scss/_variables.scss";
`
} }
} }
} }