WIP cards

This commit is contained in:
Yanis Rigaudeau 2022-11-10 00:30:52 +01:00
parent 1415da72df
commit 51a95f9bf1
Signed by: yanis
GPG Key ID: 4DD2841DF1C94D83
18 changed files with 372 additions and 23 deletions

View File

@ -4,10 +4,12 @@ import ip from 'ip';
import Server from './framework/express/server';
import UserModel from './framework/mongo/user';
import CardModel from './framework/mongo/card';
import { Config } from './config';
export type Services = {
userModel: UserModel;
cardModel: CardModel;
};
const configFile = process.env.CONFIGFILE;
@ -23,6 +25,7 @@ const db = mongo.db(config.mongo.dbName);
const services: Services = {
userModel: new UserModel(db),
cardModel: new CardModel(db),
};
const server = new Server(config.server, config.mongo, services);
@ -34,10 +37,10 @@ process.on('SIGINT', async () => {
process.exit();
});
server.start(() =>
server.start().then(() => {
console.log(
`Running on http://127.0.0.1:${config.server.port} http://${ip.address()}:${
config.server.port
}`,
),
);
);
});

30
api/src/entities/card.ts Normal file
View File

@ -0,0 +1,30 @@
import { CardCtor, CardInfo, CardRarity, CombatStats } from '@core';
import { Entity } from './entity';
export class Card extends Entity implements CardInfo {
name: string;
rarity: keyof typeof CardRarity;
stats: CombatStats;
thumbnail: string;
model: string;
constructor(raw: CardCtor) {
super(raw);
this.name = raw.name || '';
this.rarity = raw.rarity || CardRarity.COMMON;
stats: raw.stats || {};
this.thumbnail = raw.thumbnail || '';
this.model = raw.model || '';
}
Info(): CardInfo {
return {
uuid: this.uuid,
name: this.name,
rarity: this.rarity,
stats: this.stats,
thumbnail: this.thumbnail,
model: this.model,
};
}
}

View File

@ -1,4 +1,5 @@
import {
CardInfo,
UserCtor,
UserInfo,
UserInfoWithPassword,
@ -11,12 +12,14 @@ import { Entity } from './entity';
export class User extends Entity implements UserInfo {
username: string;
role: keyof typeof UserRoles;
cards: CardInfo[];
constructor(raw: UserCtor) {
super(raw);
this.username = raw.username || '';
this.role = raw.role || UserRoles.USER;
this.cards = this.cards || '';
}
Info(): UserInfo {
@ -24,6 +27,7 @@ export class User extends Entity implements UserInfo {
uuid: this.uuid,
username: this.username,
role: this.role,
cards: this.cards,
};
}
}
@ -42,6 +46,7 @@ export class UserWithPassword extends User implements UserInfoWithPassword {
uuid: this.uuid,
username: this.username,
role: this.role,
cards: this.cards,
password: this.password,
};
}

View File

@ -0,0 +1,66 @@
import { RequestHandler, Router } from 'express';
import { CreateCard, ListCards, ReadCard } from '../../functions/card';
import { Services } from '../../app';
import { CheckPermissions, getRequestId, ValidateSchema } from './middleware';
import { CreateCardSchema, ReadCardSchema } from './schema/card';
function CreateHandler(services: Services): RequestHandler {
const createCard = CreateCard(services);
return async (req, res, next) => {
try {
const card = await createCard(getRequestId(req), req.body);
res.status(200).send(card);
} catch (error) {
next({ status: 409, message: 'Card Already Exists' });
}
};
}
function ReadHandler(services: Services): RequestHandler {
const readCard = ReadCard(services);
return async (req, res, next) => {
try {
const card = await readCard(getRequestId(req), req.params.uuid);
res.status(200).send(card);
} catch (error) {
next({ status: 404, message: 'Card Not Found' });
}
};
}
function ListHandler(services: Services): RequestHandler {
const listCards = ListCards(services);
return async (req, res, next) => {
try {
const cards = await listCards(getRequestId(req));
res.status(200).send(cards);
} catch (error) {
next({ message: 'Unknown Error' });
}
};
}
export function Routes(services: Services) {
const router = Router();
router.post(
'/create',
CheckPermissions(),
CreateCardSchema(),
ValidateSchema(),
CreateHandler(services),
);
router.get(
'/read/:uuid',
CheckPermissions(),
ReadCardSchema(),
ValidateSchema(),
ReadHandler(services),
);
router.get('/list', CheckPermissions(), ListHandler(services));
return router;
}

View File

@ -56,8 +56,6 @@ export function CheckPermissions(): RequestHandler {
export function ValidateSchema(): RequestHandler {
return (req, res, next) => {
const error = validationResult(req);
const oldBody = req.body;
req.body = matchedData(req, { locations: ['body'] });
@ -67,6 +65,7 @@ export function ValidateSchema(): RequestHandler {
message: 'Unprocessable Entity',
});
const error = validationResult(req);
error.isEmpty()
? next()
: next({
@ -76,6 +75,12 @@ export function ValidateSchema(): RequestHandler {
};
}
export function NotFoundHandler(): RequestHandler {
return (req, res, next) => {
next({ status: 404, message: 'Not Found' });
};
}
export function ErrorHandler(): ErrorRequestHandler {
return (error, req, res, next) => {
error.status

View File

@ -1,11 +1,13 @@
import { Router } from 'express';
import { Services } from '../../app';
import * as user from './user';
import * as card from './card';
export function Routes(services: Services) {
const router = Router();
router.use('/user', user.Routes(services));
router.use('/card', card.Routes(services));
return router;
}

View File

@ -0,0 +1,29 @@
import { checkSchema } from 'express-validator';
import { CardRarity } from '@core';
export const CreateCardSchema = () =>
checkSchema({
name: {
isString: true,
},
rarity: {
isIn: {
options: [Object.values(CardRarity)],
},
},
thumbnail: {
isString: true,
},
model: {
isString: true,
},
});
export const ReadCardSchema = () =>
checkSchema({
uuid: {
isUUID: {
options: 4,
},
},
});

View File

@ -1,15 +1,6 @@
import { checkSchema } from 'express-validator';
import { UserRoles } from '@core';
export const ReadUserSchema = () =>
checkSchema({
uuid: {
isUUID: {
options: 4,
},
},
});
export const LoginUserSchema = () =>
checkSchema({
username: {
@ -37,6 +28,15 @@ export const CreateUserSchema = () =>
},
});
export const ReadUserSchema = () =>
checkSchema({
uuid: {
isUUID: {
options: 4,
},
},
});
export const UpdateUserSchema = () =>
checkSchema({
username: {

View File

@ -6,7 +6,7 @@ import { randomUUID } from 'crypto';
import * as http from 'http';
import { Routes } from './router';
import { RequestId, ErrorHandler } from './middleware';
import { RequestId, ErrorHandler, NotFoundHandler } from './middleware';
import { Services } from '../../app';
import { MongoConfig, ServerConfig } from '../../config';
@ -32,7 +32,6 @@ class Server {
origin: this.config.origin,
}),
);
this.app.use(RequestId());
this.app.use(
session({
proxy: process.env.NODE_ENV === 'production',
@ -52,12 +51,16 @@ class Server {
saveUninitialized: false,
}),
);
this.app.use(RequestId());
this.app.use(Routes(services));
this.app.use(NotFoundHandler());
this.app.use(ErrorHandler());
}
start(func: () => void): void {
this.server = this.app.listen(this.config.port, func);
async start(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.server = this.app.listen(this.config.port, resolve);
});
}
async close(): Promise<void> {

View File

@ -5,6 +5,7 @@ import {
ReadUser,
LoginUser,
UpdateUser,
ListUsers,
} from '../../functions/user';
import {
LoginUserSchema,
@ -83,9 +84,23 @@ function UpdateHandler(services: Services): RequestHandler {
};
}
function ListHandler(services: Services): RequestHandler {
const listUsers = ListUsers(services);
return async (req, res, next) => {
try {
const users = await listUsers(getRequestId(req));
res.status(200).send(users);
} catch (error) {
next({ message: 'Unknown Error' });
}
};
}
export function Routes(services: Services) {
const router = Router();
//User
router.post(
'/login',
LoginUserSchema(),
@ -105,7 +120,6 @@ export function Routes(services: Services) {
ValidateSchema(),
ReadHandler(services),
);
router.post(
'/create',
CheckPermissions(),
@ -113,7 +127,6 @@ export function Routes(services: Services) {
ValidateSchema(),
CreateHandler(services),
);
router.patch(
'/update/:uuid',
CheckPermissions(),
@ -121,6 +134,16 @@ export function Routes(services: Services) {
ValidateSchema(),
UpdateHandler(services),
);
router.get('/list', CheckPermissions(), ListHandler(services));
//Alteration
router.post(
'/givecard',
CheckPermissions(),
GiveCardSchema(),
ValidateSchema(),
GiveCardHandler(),
);
return router;
}

View File

@ -0,0 +1,74 @@
import { Collection, Db } from 'mongodb';
import { UpdateCardBody } from '@core';
import { Card } from '../../entities/card';
import { log } from '../../functions/logger';
class CardModel {
private collection: Collection<Card>;
constructor(db: Db) {
this.collection = db.collection<Card>('cards');
}
public async create(tracker: string, cardInfo: Card): Promise<Card> {
const checkCard = await this.collection.findOne({
uuid: cardInfo.uuid,
});
if (checkCard) {
log(tracker, 'Card Already Exists');
throw new Error();
}
await this.collection.insertOne(cardInfo);
const card = await this.read(tracker, cardInfo.uuid);
log(tracker, 'CREATE USER', card);
return card;
}
public async read(tracker: string, uuid: string): Promise<Card> {
const cardDocument = await this.collection.findOne({ uuid });
if (!cardDocument) {
log(tracker, 'Card Not Found');
throw new Error();
}
const card = new Card(cardDocument);
log(tracker, 'READ CARD', card);
return card;
}
public async update(
tracker: string,
uuid: string,
cardInfo: UpdateCardBody,
): Promise<Card> {
const checkCard = await this.collection.findOne({ uuid });
if (!checkCard) {
log(tracker, 'Card Does Not Exist');
throw new Error();
}
await this.collection.updateOne(
{ uuid },
{
$set: {
...cardInfo,
updatedAt: new Date(),
},
},
);
const card = await this.read(tracker, uuid);
log(tracker, 'UPDATE CARD', card);
return card;
}
public async list(tracker: string): Promise<Card[]> {
const cardCollection = await this.collection.find().toArray();
log(tracker, 'LIST CARDS');
return cardCollection.map((cardDocument) => new Card(cardDocument));
}
}
export default CardModel;

View File

@ -11,9 +11,9 @@ class UserModel {
this.collection = db.collection<User>('users');
}
public async login(tracker: string, data: LoginUserBody): Promise<User> {
public async login(tracker: string, userInfo: LoginUserBody): Promise<User> {
const checkUser = await this.collection.findOne({
username: data.username,
username: userInfo.username,
});
if (!checkUser) {
log(tracker, 'User Not Found');
@ -22,7 +22,7 @@ class UserModel {
const userDocument = await this.collection.findOne({
uuid: checkUser.uuid,
password: getHashedPassword(checkUser.uuid, data.password),
password: getHashedPassword(checkUser.uuid, userInfo.password),
});
if (!userDocument) {
log(tracker, 'Wrong Password');
@ -93,6 +93,12 @@ class UserModel {
log(tracker, 'UPDATE USER', user);
return user;
}
public async list(tracker: string): Promise<User[]> {
const userCollection = await this.collection.find().toArray();
log(tracker, 'LIST USERS');
return userCollection.map((userDocument) => new User(userDocument));
}
}
export default UserModel;

36
api/src/functions/card.ts Normal file
View File

@ -0,0 +1,36 @@
import { CardInfo, CreateCardBody } from '@core';
import { Card } from '../entities/card';
import { Services } from '../app';
export function CreateCard(
services: Services,
): (tracker: string, raw: CreateCardBody) => Promise<CardInfo> {
const { cardModel } = services;
return async (tracker, raw) => {
const card = await cardModel.create(tracker, new Card(raw));
return card.Info();
};
}
export function ReadCard(
services: Services,
): (tracker: string, uuid: string) => Promise<CardInfo> {
const { cardModel } = services;
return async (tracker, uuid) => {
const card = await cardModel.read(tracker, uuid);
return card.Info();
};
}
export function ListCards(
services: Services,
): (tracker: string) => Promise<CardInfo[]> {
const { cardModel } = services;
return async (tracker) => {
const cards = await cardModel.list(tracker);
return cards.map((card) => card.Info());
};
}

View File

@ -45,3 +45,14 @@ export function UpdateUser(
return user.Info();
};
}
export function ListUsers(
services: Services,
): (tracker: string) => Promise<UserInfo[]> {
const { userModel } = services;
return async (tracker) => {
const users = await userModel.list(tracker);
return users.map((user) => user.Info());
};
}

12
core/src/alteration.ts Normal file
View File

@ -0,0 +1,12 @@
import { EntityInfo } from './entity';
import { CardInfo } from './card';
export type AlerationInfo = {
card: CardInfo;
level: number;
xp: number;
};
export type Ateration = AlerationInfo & EntityInfo;
export type AlerationCtor = Partial<Ateration>;

40
core/src/card.ts Normal file
View File

@ -0,0 +1,40 @@
import { EntityInfo } from './entity';
export enum CardRarity {
COMMON = 'COMMON',
RARE = 'RARE',
EPIC = 'EPIC',
LEGENDARY = 'LEGENDARY',
}
export type CombatStats = {
baseAtk: number;
baseDef: number;
baseCrit: number;
multAtk: number;
multDef: number;
multCrit: number;
};
export type CardInfo = {
uuid: string;
name: string;
rarity: keyof typeof CardRarity;
stats: CombatStats;
thumbnail: string;
model: string;
};
export type CreateCardBody = {
name: string;
rarity: keyof typeof CardRarity;
stats: CombatStats;
thumbnail: string;
model: string;
};
export type Card = CardInfo & EntityInfo;
export type CardCtor = Partial<Card>;
export type UpdateCardBody = Partial<Omit<CardInfo, 'uuid'>>;

View File

@ -1,2 +1,4 @@
export * from './alteration';
export * from './card';
export * from './user';
export * from './entity';

View File

@ -1,4 +1,5 @@
import { EntityInfo } from './entity';
import { CardInfo } from './card';
export enum UserRoles {
ADMIN = 'ADMIN',
@ -9,6 +10,7 @@ export type UserInfo = {
uuid: string;
username: string;
role: keyof typeof UserRoles;
cards: CardInfo[];
};
export type User = UserInfo & EntityInfo;