refactor: restructure to monorepo with backends and apiTest

- Move web/backend to backends/potter-api
- Move web/frontend to backends/potter-api/frontend
- Add root package.json with npm workspaces
- Update README.md with monorepo navigation
- Update .github/workflows/api-tests.yml for dynamic test discovery
- Update bruno_test_scenario.md paths
- Fix .gitignore to allow JSON data files
- Add three new AI tests in apiTest/PotterAPI/AI/

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
kankys committed 2026-09-15 21:08:39 +02:00
1 parent 2e185b47ef
commit d0cdabbf11
57 files changed
+292 -35

No files matched your search

@@ -0,0 +1,145 @@
const fs = require('fs');
const path = require('path');
const charactersFilePath = path.join(__dirname, '../characters.json');
var characters = require('../characters.json');
const joi = require('joi');
const initialCharacters = JSON.parse(JSON.stringify(require('../characters.json')));
const randomId = require('random-id');
const schema = joi.object().keys({
id: joi.any(),
name: joi.string().min(2).max(100).required(),
role: joi.string().allow('', null),
house: joi.string().allow('', null),
school: joi.string().allow('', null),
ministryOfMagic: joi.boolean().default(false),
orderOfThePhoenix: joi.boolean().default(false),
dumbledoresArmy: joi.boolean().default(false),
deathEater: joi.boolean().default(false),
bloodStatus: joi.string().allow('', null),
species: joi.string().allow('', null),
wand: joi.string().allow('', null),
boggart: joi.string().allow('', null),
patronus: joi.string().allow('', null),
animagus: joi.string().allow('', null),
alias: joi.string().allow('', null)
});
function saveCharactersToFile() {
fs.writeFileSync(charactersFilePath, JSON.stringify(characters, null, 4));
}
exports.characters_list = function (req, res) {
let result = characters;
if (typeof req.query.house !== 'undefined') {
result = result.filter(c => c.house === req.query.house);
}
if (typeof req.query.role !== 'undefined') {
result = result.filter(c => c.role === req.query.role);
}
if (typeof req.query.bloodStatus !== 'undefined') {
result = result.filter(c => c.bloodStatus === req.query.bloodStatus);
}
if (typeof req.query.species !== 'undefined') {
result = result.filter(c => c.species === req.query.species);
}
if (typeof req.query.limit !== 'undefined') {
result = result.slice(0, req.query.limit);
}
return res.json(result);
};
exports.specific_character = function (req, res) {
let result = characters.find(c => c.id == req.params.characterId);
if (typeof result !== 'undefined') {
return res.send(result);
}
return res.status(404).send({ message: "Character not found" });
};
exports.delete_all = function (req, res) {
characters = [];
saveCharactersToFile();
return res.send({ message: 'All characters deleted' });
};
exports.reset = function (req, res) {
characters = JSON.parse(JSON.stringify(initialCharacters));
saveCharactersToFile();
return res.send({ message: 'Characters reset', characters: characters });
};
exports.delete_character = function (req, res) {
let result = characters.find(c => c.id == req.params.characterId);
if (typeof result === 'undefined') {
return res.status(404).send({ message: "Character not found" });
}
characters = characters.filter(c => c.id != req.params.characterId);
saveCharactersToFile();
return res.send({ message: 'Character deleted' });
};
exports.update_character = function (req, res) {
let result = characters.find(c => c.id == req.params.characterId);
if (typeof result === 'undefined') {
return res.status(404).send({ message: "Character not found" });
}
var newChar = Object.assign({}, req.body, { id: result.id });
const validation = schema.validate(newChar);
const { value, error } = validation;
if (error) {
return res.status(422).json({
message: 'Invalid character request',
data: error
});
}
const formattedCharacter = Object.assign({ id: value.id, name: value.name }, value);
characters = characters.filter(c => c.id != req.params.characterId);
characters.push(formattedCharacter);
saveCharactersToFile();
return res.status(201).json({
message: "Character updated",
character: { id: formattedCharacter.id }
});
};
exports.new_character = function (req, res) {
var len = 24;
var pattern = '0123456789abcdef';
var newChar = Object.assign({}, req.body, { id: randomId(len, pattern) });
let existing = characters.find(c => c.name === newChar.name);
if (typeof existing !== 'undefined') {
return res.status(400).send({
message: 'Character ' + newChar.name + ' already exists'
});
}
const validation = schema.validate(newChar);
const { value, error } = validation;
if (error) {
return res.status(422).json({
message: 'Invalid request',
data: error
});
}
const formattedCharacter = Object.assign({ id: value.id, name: value.name }, value);
characters.push(formattedCharacter);
saveCharactersToFile();
return res.status(201).json({
message: "Character created",
character: { id: formattedCharacter.id }
});
};
@@ -0,0 +1,14 @@
const houses = require('../houses.json');
exports.houses_list = function (req, res) {
res.json(houses);
}
exports.specific_house = function (req, res) {
let result = houses.find(house => house.id == req.params.houseId);
if (typeof result == 'undefined') {
res.status(404)
res.send({ message: "House not found" });
}
res.send(result);
}
@@ -0,0 +1,161 @@
const fs = require('fs');
const path = require('path');
const spellsFilePath = path.join(__dirname, '../spells.json');
var spells = require('../spells.json');
const joi = require('joi');
const initialSpells = JSON.parse(JSON.stringify(require('../spells.json')));
const schema = joi.object().keys({
spell: joi.string().min(3).max(30).required().regex(/^\w+(?:\s+\w+)*$/),
type: joi.string().valid('Charm', 'Enchantment', 'Curse', 'Spell', 'Hex', 'Jinx'),
effect: joi.string().min(4),
isUnforgivable: joi.boolean().required(),
id: joi.any()
});
const randomId = require('random-id');
function saveSpellsToFile() {
fs.writeFileSync(spellsFilePath, JSON.stringify(spells, null, 2));
}
exports.spells_list = function (req, res) {
let result = spells;
if (typeof req.query.type !== 'undefined') {
result = spells.filter(spell => spell.type == req.query.type)
}
if (typeof req.query.isUnforgivable !== 'undefined') {
result = result.filter(spell => spell.isUnforgivable == req.query.isUnforgivable)
}
if (typeof req.query.limit !== 'undefined') {
result = result.slice(0, req.query.limit);
}
setTimeout(() => {
return res.json(result);
}, 100)
}
exports.delete_all = function (req, res) {
spells = [];
saveSpellsToFile();
res.send({ message: 'Mischief managed' })
}
exports.reset = function (req, res) {
spells = JSON.parse(JSON.stringify(initialSpells));
saveSpellsToFile();
res.send({ message: 'Aparecium', spells: spells })
}
exports.specific_spell = function (req, res) {
let result = spells.find(spell => spell.id == req.params.spellId);
if (typeof result !== 'undefined') {
return res.send(result);
}
return res.status(404).send({ message: "Spell not found" });
}
exports.delete_spell = function (req, res) {
let result = spells.find(spell => spell.id == req.params.spellId);
if (typeof result == 'undefined') {
return res.status(404).send({ message: "Spell not found" });
}
spells = spells.filter(spell => spell.id != req.params.spellId);
saveSpellsToFile();
return res.send({
message: 'spell deleted'
});
}
exports.update_spell = function (req, res) {
let result = spells.find(spell => spell.id == req.params.spellId);
if (typeof result == 'undefined') {
return res.status(404).send({ message: "Spell not found" });
}
var newSpell = {
spell: req.body.spell,
type: req.body.type,
effect: req.body.effect,
isUnforgivable: req.body.isUnforgivable,
id: result.id
}
const validation = schema.validate(newSpell);
const { value, error } = validation;
const valid = error == null;
if (!valid) {
return res.status(422).json({
message: 'Wizard, I\'m sory but your request is invalid',
data: error
})
}
const formattedSpell = {
id: value.id,
spell: value.spell,
type: value.type,
effect: value.effect,
isUnforgivable: value.isUnforgivable
};
spells = spells.filter(spell => spell.id != req.params.spellId);
spells.push(formattedSpell);
saveSpellsToFile();
return res.status(201).json({
message: "Spell updated",
spell: {
id: formattedSpell.id
}
});
}
exports.new_spell = function (req, res) {
var len = 30;
var pattern = 'aA0'
var newSpell = {
id: randomId(len, pattern),
spell: req.body.spell,
type: req.body.type,
effect: req.body.effect,
isUnforgivable: req.body.isUnforgivable
}
let result = spells.find((spell) => spell.spell === newSpell.spell);
if (typeof result !== 'undefined') {
return res.status(400).send({
message: 'Spell ' + newSpell.spell + ' already exists'
})
}
const validation = schema.validate(newSpell);
const { value, error } = validation;
const valid = error == null;
if (!valid) {
return res.status(422).json({
message: 'Invalid request',
data: error
})
}
const formattedSpell = {
id: value.id,
spell: value.spell,
type: value.type,
effect: value.effect,
isUnforgivable: value.isUnforgivable
};
spells.push(formattedSpell);
saveSpellsToFile();
return res.status(201).json({
message: "Spell created",
spell: {
id: formattedSpell.id
}
});
}