Compare commits
12 Commits
78addafe18
...
card-wip
Author | SHA1 | Date | |
---|---|---|---|
e86d909e07
|
|||
39ba4856a2 | |||
51a95f9bf1
|
|||
1415da72df | |||
0bb28ce89a
|
|||
5c21712344
|
|||
82d356ef5e
|
|||
46a7e07b86
|
|||
fd335ce5cd | |||
a4f5cf18d8 | |||
e9e1b48970
|
|||
0f53063832
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@
|
|||||||
**/config
|
**/config
|
||||||
|
|
||||||
!.prettierrc.json
|
!.prettierrc.json
|
||||||
|
!.insomnia
|
||||||
|
8
.insomnia/ApiSpec/spc_8436777c430547adb1085fd68e18eace.yml
Executable file
8
.insomnia/ApiSpec/spc_8436777c430547adb1085fd68e18eace.yml
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
_id: spc_8436777c430547adb1085fd68e18eace
|
||||||
|
type: ApiSpec
|
||||||
|
parentId: wrk_01922deddc7342ddb4d070126530214b
|
||||||
|
modified: 1666813426216
|
||||||
|
created: 1666813426216
|
||||||
|
fileName: Rate
|
||||||
|
contents: ""
|
||||||
|
contentType: yaml
|
14
.insomnia/Environment/env_25eaa5dc66424aeabb57ef52fb713da8.yml
Executable file
14
.insomnia/Environment/env_25eaa5dc66424aeabb57ef52fb713da8.yml
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
_id: env_25eaa5dc66424aeabb57ef52fb713da8
|
||||||
|
type: Environment
|
||||||
|
parentId: env_9b022c4edb1e6f6aa558cd2b00817d97d73ac78a
|
||||||
|
modified: 1666813483038
|
||||||
|
created: 1666813431942
|
||||||
|
name: local
|
||||||
|
data:
|
||||||
|
URL: http://localhost:8000
|
||||||
|
dataPropertyOrder:
|
||||||
|
"&":
|
||||||
|
- URL
|
||||||
|
color: null
|
||||||
|
isPrivate: false
|
||||||
|
metaSortKey: 1666813431942
|
14
.insomnia/Environment/env_589a320c58d54efc8e9ab96f6506ebd0.yml
Executable file
14
.insomnia/Environment/env_589a320c58d54efc8e9ab96f6506ebd0.yml
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
_id: env_589a320c58d54efc8e9ab96f6506ebd0
|
||||||
|
type: Environment
|
||||||
|
parentId: env_9b022c4edb1e6f6aa558cd2b00817d97d73ac78a
|
||||||
|
modified: 1666813482405
|
||||||
|
created: 1666813453631
|
||||||
|
name: yayeet.cf
|
||||||
|
data:
|
||||||
|
URL: https://html5.yayeet.cf/api
|
||||||
|
dataPropertyOrder:
|
||||||
|
"&":
|
||||||
|
- URL
|
||||||
|
color: null
|
||||||
|
isPrivate: false
|
||||||
|
metaSortKey: 1666813453631
|
11
.insomnia/Environment/env_9b022c4edb1e6f6aa558cd2b00817d97d73ac78a.yml
Executable file
11
.insomnia/Environment/env_9b022c4edb1e6f6aa558cd2b00817d97d73ac78a.yml
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
_id: env_9b022c4edb1e6f6aa558cd2b00817d97d73ac78a
|
||||||
|
type: Environment
|
||||||
|
parentId: wrk_01922deddc7342ddb4d070126530214b
|
||||||
|
modified: 1666813430220
|
||||||
|
created: 1666813426222
|
||||||
|
name: Base Environment
|
||||||
|
data: {}
|
||||||
|
dataPropertyOrder: {}
|
||||||
|
color: null
|
||||||
|
isPrivate: false
|
||||||
|
metaSortKey: 1666813426222
|
21
.insomnia/Request/req_7a775a1b9142457eb390ccd6b2e72f28.yml
Executable file
21
.insomnia/Request/req_7a775a1b9142457eb390ccd6b2e72f28.yml
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
_id: req_7a775a1b9142457eb390ccd6b2e72f28
|
||||||
|
type: Request
|
||||||
|
parentId: fld_c78e326c656f4411b8899877dd4f3b9c
|
||||||
|
modified: 1666813549000
|
||||||
|
created: 1666813535408
|
||||||
|
url: "{{ _.URL }}/user/logout"
|
||||||
|
name: Logout
|
||||||
|
description: ""
|
||||||
|
method: POST
|
||||||
|
body: {}
|
||||||
|
parameters: []
|
||||||
|
headers: []
|
||||||
|
authentication: {}
|
||||||
|
metaSortKey: -1666813492452
|
||||||
|
isPrivate: false
|
||||||
|
settingStoreCookies: true
|
||||||
|
settingSendCookies: true
|
||||||
|
settingDisableRenderRequestBody: false
|
||||||
|
settingEncodeUrl: true
|
||||||
|
settingRebuildPath: true
|
||||||
|
settingFollowRedirects: global
|
30
.insomnia/Request/req_b0a828e3ee4d4267bd036b10d6bb1bed.yml
Executable file
30
.insomnia/Request/req_b0a828e3ee4d4267bd036b10d6bb1bed.yml
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
_id: req_b0a828e3ee4d4267bd036b10d6bb1bed
|
||||||
|
type: Request
|
||||||
|
parentId: fld_c78e326c656f4411b8899877dd4f3b9c
|
||||||
|
modified: 1667756731637
|
||||||
|
created: 1666813566609
|
||||||
|
url: "{{ _.URL }}/user/create"
|
||||||
|
name: Create
|
||||||
|
description: ""
|
||||||
|
method: POST
|
||||||
|
body:
|
||||||
|
mimeType: application/json
|
||||||
|
text: |-
|
||||||
|
{
|
||||||
|
"username": "test2",
|
||||||
|
"password": "coucou",
|
||||||
|
"role": "USER"
|
||||||
|
}
|
||||||
|
parameters: []
|
||||||
|
headers:
|
||||||
|
- name: Content-Type
|
||||||
|
value: application/json
|
||||||
|
authentication: {}
|
||||||
|
metaSortKey: -1666813492352
|
||||||
|
isPrivate: false
|
||||||
|
settingStoreCookies: true
|
||||||
|
settingSendCookies: true
|
||||||
|
settingDisableRenderRequestBody: false
|
||||||
|
settingEncodeUrl: true
|
||||||
|
settingRebuildPath: true
|
||||||
|
settingFollowRedirects: global
|
21
.insomnia/Request/req_bf97a9e369584040ac3a8440c7b2bc7c.yml
Executable file
21
.insomnia/Request/req_bf97a9e369584040ac3a8440c7b2bc7c.yml
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
_id: req_bf97a9e369584040ac3a8440c7b2bc7c
|
||||||
|
type: Request
|
||||||
|
parentId: fld_c78e326c656f4411b8899877dd4f3b9c
|
||||||
|
modified: 1666813598408
|
||||||
|
created: 1666813551357
|
||||||
|
url: "{{ _.URL }}/user/read/ff08bc79-ba76-432d-9709-34eefbc462eb"
|
||||||
|
name: Read
|
||||||
|
description: ""
|
||||||
|
method: GET
|
||||||
|
body: {}
|
||||||
|
parameters: []
|
||||||
|
headers: []
|
||||||
|
authentication: {}
|
||||||
|
metaSortKey: -1666813492402
|
||||||
|
isPrivate: false
|
||||||
|
settingStoreCookies: true
|
||||||
|
settingSendCookies: true
|
||||||
|
settingDisableRenderRequestBody: false
|
||||||
|
settingEncodeUrl: true
|
||||||
|
settingRebuildPath: true
|
||||||
|
settingFollowRedirects: global
|
30
.insomnia/Request/req_dbbe63eee56d439bb93f4c4929fcbfb2.yml
Executable file
30
.insomnia/Request/req_dbbe63eee56d439bb93f4c4929fcbfb2.yml
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
_id: req_dbbe63eee56d439bb93f4c4929fcbfb2
|
||||||
|
type: Request
|
||||||
|
parentId: fld_c78e326c656f4411b8899877dd4f3b9c
|
||||||
|
modified: 1667756736645
|
||||||
|
created: 1666813492501
|
||||||
|
url: "{{ _.URL }}/user/login/"
|
||||||
|
name: Login
|
||||||
|
description: ""
|
||||||
|
method: POST
|
||||||
|
body:
|
||||||
|
mimeType: application/json
|
||||||
|
text: |-
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "motdepasse"
|
||||||
|
}
|
||||||
|
parameters: []
|
||||||
|
headers:
|
||||||
|
- name: Content-Type
|
||||||
|
value: application/json
|
||||||
|
id: pair_32919a49053545f5b23698f4c5724a56
|
||||||
|
authentication: {}
|
||||||
|
metaSortKey: -1666813492502
|
||||||
|
isPrivate: false
|
||||||
|
settingStoreCookies: true
|
||||||
|
settingSendCookies: true
|
||||||
|
settingDisableRenderRequestBody: false
|
||||||
|
settingEncodeUrl: true
|
||||||
|
settingRebuildPath: true
|
||||||
|
settingFollowRedirects: global
|
28
.insomnia/Request/req_f2d435a1ee5b47b1b7b6e2be9db8a8bb.yml
Executable file
28
.insomnia/Request/req_f2d435a1ee5b47b1b7b6e2be9db8a8bb.yml
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
_id: req_f2d435a1ee5b47b1b7b6e2be9db8a8bb
|
||||||
|
type: Request
|
||||||
|
parentId: fld_c78e326c656f4411b8899877dd4f3b9c
|
||||||
|
modified: 1667756731065
|
||||||
|
created: 1667753525832
|
||||||
|
url: "{{ _.URL }}/user/update/ff08bc79-ba76-432d-9709-34eefbc462eb"
|
||||||
|
name: Update
|
||||||
|
description: ""
|
||||||
|
method: PATCH
|
||||||
|
body:
|
||||||
|
mimeType: application/json
|
||||||
|
text: |-
|
||||||
|
{
|
||||||
|
"username": "admin2"
|
||||||
|
}
|
||||||
|
parameters: []
|
||||||
|
headers:
|
||||||
|
- name: Content-Type
|
||||||
|
value: application/json
|
||||||
|
authentication: {}
|
||||||
|
metaSortKey: -1666813492302
|
||||||
|
isPrivate: false
|
||||||
|
settingStoreCookies: true
|
||||||
|
settingSendCookies: true
|
||||||
|
settingDisableRenderRequestBody: false
|
||||||
|
settingEncodeUrl: true
|
||||||
|
settingRebuildPath: true
|
||||||
|
settingFollowRedirects: global
|
10
.insomnia/RequestGroup/fld_c78e326c656f4411b8899877dd4f3b9c.yml
Executable file
10
.insomnia/RequestGroup/fld_c78e326c656f4411b8899877dd4f3b9c.yml
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
_id: fld_c78e326c656f4411b8899877dd4f3b9c
|
||||||
|
type: RequestGroup
|
||||||
|
parentId: wrk_01922deddc7342ddb4d070126530214b
|
||||||
|
modified: 1666813489256
|
||||||
|
created: 1666813489256
|
||||||
|
name: User
|
||||||
|
description: ""
|
||||||
|
environment: {}
|
||||||
|
environmentPropertyOrder: null
|
||||||
|
metaSortKey: -1666813489256
|
8
.insomnia/Workspace/wrk_01922deddc7342ddb4d070126530214b.yml
Executable file
8
.insomnia/Workspace/wrk_01922deddc7342ddb4d070126530214b.yml
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
_id: wrk_01922deddc7342ddb4d070126530214b
|
||||||
|
type: Workspace
|
||||||
|
parentId: null
|
||||||
|
modified: 1666813426216
|
||||||
|
created: 1666813426216
|
||||||
|
name: Rate
|
||||||
|
description: ""
|
||||||
|
scope: design
|
@ -1,19 +1,21 @@
|
|||||||
import './paths';
|
import './paths';
|
||||||
import Server from './framework/express/server';
|
|
||||||
import ip from 'ip';
|
|
||||||
import UserModel from './framework/mongo/user';
|
|
||||||
import { MongoClient } from 'mongodb';
|
import { MongoClient } from 'mongodb';
|
||||||
|
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';
|
import { Config } from './config';
|
||||||
import { exit, env } from 'process';
|
|
||||||
|
|
||||||
export type Services = {
|
export type Services = {
|
||||||
userModel: UserModel;
|
userModel: UserModel;
|
||||||
|
cardModel: CardModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
const configFile = env.CONFIGFILE;
|
const configFile = process.env.CONFIGFILE;
|
||||||
if (configFile === undefined) {
|
if (configFile === undefined) {
|
||||||
console.log('env var CONFIGFILE not set');
|
console.log('env var CONFIGFILE not set');
|
||||||
exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = new Config(configFile);
|
const config = new Config(configFile);
|
||||||
@ -23,14 +25,22 @@ const db = mongo.db(config.mongo.dbName);
|
|||||||
|
|
||||||
const services: Services = {
|
const services: Services = {
|
||||||
userModel: new UserModel(db),
|
userModel: new UserModel(db),
|
||||||
|
cardModel: new CardModel(db),
|
||||||
};
|
};
|
||||||
|
|
||||||
const server = new Server(config.server, config.mongo, services);
|
const server = new Server(config.server, config.mongo, services);
|
||||||
|
|
||||||
server.start(() =>
|
process.on('SIGINT', async () => {
|
||||||
|
console.log('\nClosing Api...');
|
||||||
|
await mongo.close();
|
||||||
|
await server.close();
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.start().then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
`Running on http://127.0.0.1:${config.server.port} http://${ip.address()}:${
|
`Running on http://127.0.0.1:${config.server.port} http://${ip.address()}:${
|
||||||
config.server.port
|
config.server.port
|
||||||
}`,
|
}`,
|
||||||
),
|
);
|
||||||
);
|
});
|
||||||
|
@ -16,6 +16,15 @@ export class Config {
|
|||||||
|
|
||||||
constructor(configFile: string) {
|
constructor(configFile: string) {
|
||||||
const config = JSON.parse(readFileSync(configFile).toString()) as Config;
|
const config = JSON.parse(readFileSync(configFile).toString()) as Config;
|
||||||
|
if (
|
||||||
|
config.mongo.dbName === undefined ||
|
||||||
|
config.mongo.uri === undefined ||
|
||||||
|
config.server.origin === undefined ||
|
||||||
|
config.server.port === undefined
|
||||||
|
) {
|
||||||
|
console.log('Error in Config File');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
this.server = config.server;
|
this.server = config.server;
|
||||||
this.mongo = config.mongo;
|
this.mongo = config.mongo;
|
||||||
}
|
}
|
||||||
|
37
api/src/entities/card.ts
Normal file
37
api/src/entities/card.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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;
|
||||||
|
this.stats = raw.stats || {
|
||||||
|
baseAtk: 0,
|
||||||
|
baseCrit: 0,
|
||||||
|
baseDef: 0,
|
||||||
|
multAtk: 0,
|
||||||
|
multCrit: 0,
|
||||||
|
multDef: 0,
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,15 @@
|
|||||||
import { EntityCtor, EntityInfo } from '@core';
|
import { EntityCtor, EntityInfo } from '@core';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
export class Entity implements EntityInfo {
|
export class Entity implements EntityInfo {
|
||||||
|
_id: ObjectId;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
constructor(raw: EntityCtor) {
|
constructor(raw: EntityCtor) {
|
||||||
|
this._id = raw._id || new ObjectId();
|
||||||
this.uuid = raw.uuid || randomUUID();
|
this.uuid = raw.uuid || randomUUID();
|
||||||
this.createdAt = raw.createdAt || new Date();
|
this.createdAt = raw.createdAt || new Date();
|
||||||
this.updatedAt = raw.updatedAt || new Date();
|
this.updatedAt = raw.updatedAt || new Date();
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
import {
|
import {
|
||||||
|
CardInfo,
|
||||||
UserCtor,
|
UserCtor,
|
||||||
UserInfo,
|
UserInfo,
|
||||||
UserInfoWithPassword,
|
UserInfoWithPassword,
|
||||||
UserRoles,
|
UserRoles,
|
||||||
UserWithPasswordCtor,
|
UserWithPasswordCtor,
|
||||||
} from '@core';
|
} from '@core';
|
||||||
import { generateHash } from '../functions/password';
|
import { getHashedPassword } from '../functions/password';
|
||||||
|
import { Card } from './card';
|
||||||
import { Entity } from './entity';
|
import { Entity } from './entity';
|
||||||
|
|
||||||
export class User extends Entity implements UserInfo {
|
export class User extends Entity implements UserInfo {
|
||||||
username: string;
|
username: string;
|
||||||
role: keyof typeof UserRoles;
|
role: keyof typeof UserRoles;
|
||||||
|
cards: Card[];
|
||||||
|
|
||||||
constructor(raw: UserCtor) {
|
constructor(raw: UserCtor) {
|
||||||
super(raw);
|
super(raw);
|
||||||
|
|
||||||
this.username = raw.username || '';
|
this.username = raw.username || '';
|
||||||
this.role = raw.role || UserRoles.USER;
|
this.role = raw.role || UserRoles.USER;
|
||||||
|
this.cards = raw.cards ? raw.cards.map(card => new Card(card)) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Info(): UserInfo {
|
Info(): UserInfo {
|
||||||
@ -24,6 +28,7 @@ export class User extends Entity implements UserInfo {
|
|||||||
uuid: this.uuid,
|
uuid: this.uuid,
|
||||||
username: this.username,
|
username: this.username,
|
||||||
role: this.role,
|
role: this.role,
|
||||||
|
cards: this.cards.map(card => card.Info()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,7 +39,7 @@ export class UserWithPassword extends User implements UserInfoWithPassword {
|
|||||||
constructor(raw: UserWithPasswordCtor) {
|
constructor(raw: UserWithPasswordCtor) {
|
||||||
super(raw);
|
super(raw);
|
||||||
|
|
||||||
this.password = generateHash(this.uuid, raw.password || '');
|
this.password = getHashedPassword(this.uuid, raw.password || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
Info(): UserInfoWithPassword {
|
Info(): UserInfoWithPassword {
|
||||||
@ -42,6 +47,7 @@ export class UserWithPassword extends User implements UserInfoWithPassword {
|
|||||||
uuid: this.uuid,
|
uuid: this.uuid,
|
||||||
username: this.username,
|
username: this.username,
|
||||||
role: this.role,
|
role: this.role,
|
||||||
|
cards: this.cards.map(card => card.Info()),
|
||||||
password: this.password,
|
password: this.password,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
66
api/src/framework/express/card.ts
Normal file
66
api/src/framework/express/card.ts
Normal 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;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { ErrorRequestHandler, Request, RequestHandler } from 'express';
|
import { ErrorRequestHandler, Request, RequestHandler } from 'express';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { validationResult } from 'express-validator';
|
import { validationResult, matchedData } from 'express-validator';
|
||||||
import { UserInfo, UserRoles } from '@core';
|
import { UserInfo, UserRoles } from '@core';
|
||||||
|
|
||||||
declare module 'express-session' {
|
declare module 'express-session' {
|
||||||
@ -9,22 +9,22 @@ declare module 'express-session' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getId(req: Request): string {
|
export function getRequestId(req: Request): string {
|
||||||
return req.header('request-id') || 'unknown';
|
return req.header('x-request-id') || 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RequestId(): RequestHandler {
|
export function RequestId(): RequestHandler {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
req.headers['request-id'] = randomUUID();
|
req.headers['x-request-id'] = randomUUID();
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CheckPermissions(): RequestHandler {
|
export function CheckPermissions(): RequestHandler {
|
||||||
function getResourceId(req: Request): string | null {
|
function getResourceId(req: Request): string | undefined {
|
||||||
if (req.params.uuid) return req.params.uuid;
|
if (req.params.uuid) return req.params.uuid;
|
||||||
if (req.body.uuid) return req.body.uuid;
|
if (req.body.uuid) return req.body.uuid;
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function canAccessRessource(user: UserInfo, uuid: string): boolean {
|
function canAccessRessource(user: UserInfo, uuid: string): boolean {
|
||||||
@ -34,34 +34,37 @@ export function CheckPermissions(): RequestHandler {
|
|||||||
|
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
next({ status: 401, messsage: 'Unauthorized' });
|
return next({ status: 401, messsage: 'Unauthorized' });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.session.user.role === UserRoles.ADMIN) {
|
if (req.session.user.role === UserRoles.ADMIN) {
|
||||||
next();
|
return next();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ressourceId = getResourceId(req);
|
const ressourceId = getResourceId(req);
|
||||||
if (!ressourceId) {
|
if (!ressourceId) {
|
||||||
next({ status: 403, messsage: 'Forbidden' });
|
return next({ status: 403, messsage: 'Forbidden' });
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (canAccessRessource(req.session.user, ressourceId)) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
next({ status: 403, messsage: 'Forbidden' });
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next({ status: 401, messsage: 'Unauthorized' });
|
if (canAccessRessource(req.session.user, ressourceId)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
next({ status: 403, messsage: 'Forbidden' });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SchemaValidator(): RequestHandler {
|
export function ValidateSchema(): RequestHandler {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
|
const oldBody = req.body;
|
||||||
|
req.body = matchedData(req, { locations: ['body'] });
|
||||||
|
|
||||||
|
if (JSON.stringify(oldBody) !== JSON.stringify(req.body))
|
||||||
|
return next({
|
||||||
|
status: 422,
|
||||||
|
message: 'Unprocessable Entity',
|
||||||
|
});
|
||||||
|
|
||||||
const error = validationResult(req);
|
const error = validationResult(req);
|
||||||
error.isEmpty()
|
error.isEmpty()
|
||||||
? next()
|
? next()
|
||||||
@ -72,10 +75,16 @@ export function SchemaValidator(): RequestHandler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function NotFoundHandler(): RequestHandler {
|
||||||
|
return (req, res, next) => {
|
||||||
|
next({ status: 404, message: 'Not Found' });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function ErrorHandler(): ErrorRequestHandler {
|
export function ErrorHandler(): ErrorRequestHandler {
|
||||||
return (error, req, res, next) => {
|
return (error, req, res, next) => {
|
||||||
error.status
|
error.status
|
||||||
? res.status(error.status).send(error)
|
? res.status(error.status).send(error)
|
||||||
: res.status(500).send(error);
|
: res.status(500).send({ status: 500, message: error.message });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { Services } from '../../app';
|
import { Services } from '../../app';
|
||||||
import * as user from './user';
|
import * as user from './user';
|
||||||
|
import * as card from './card';
|
||||||
|
|
||||||
export function Routes(services: Services) {
|
export function Routes(services: Services) {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.use('/user', user.Routes(services));
|
router.use('/user', user.Routes(services));
|
||||||
|
router.use('/card', card.Routes(services));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
29
api/src/framework/express/schema/card.ts
Normal file
29
api/src/framework/express/schema/card.ts
Normal 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
@ -1,15 +1,6 @@
|
|||||||
import { checkSchema } from 'express-validator';
|
import { checkSchema } from 'express-validator';
|
||||||
import { UserRoles } from '@core';
|
import { UserRoles } from '@core';
|
||||||
|
|
||||||
export const ReadUserSchema = () =>
|
|
||||||
checkSchema({
|
|
||||||
uuid: {
|
|
||||||
isUUID: {
|
|
||||||
options: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const LoginUserSchema = () =>
|
export const LoginUserSchema = () =>
|
||||||
checkSchema({
|
checkSchema({
|
||||||
username: {
|
username: {
|
||||||
@ -36,3 +27,65 @@ export const CreateUserSchema = () =>
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const ReadUserSchema = () =>
|
||||||
|
checkSchema({
|
||||||
|
uuid: {
|
||||||
|
isUUID: {
|
||||||
|
options: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ListUsersSchema = () => checkSchema({});
|
||||||
|
|
||||||
|
export const UpdateUserSchema = () =>
|
||||||
|
checkSchema({
|
||||||
|
uuid: {
|
||||||
|
isUUID: {
|
||||||
|
options: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
isString: true,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
isString: true,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
isIn: {
|
||||||
|
options: [Object.values(UserRoles)],
|
||||||
|
},
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AddCardToUserSchema = () =>
|
||||||
|
checkSchema({
|
||||||
|
uuid: {
|
||||||
|
isUUID: {
|
||||||
|
options: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cardUuid: {
|
||||||
|
isUUID: {
|
||||||
|
options: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RemoveCardFromUserSchema = () =>
|
||||||
|
checkSchema({
|
||||||
|
uuid: {
|
||||||
|
isUUID: {
|
||||||
|
options: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cardUuid: {
|
||||||
|
isUUID: {
|
||||||
|
options: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import express, { Express } from 'express';
|
import express, { Express } from 'express';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
|
import MongoStore from 'connect-mongo';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import * as http from 'http';
|
||||||
|
|
||||||
import { Routes } from './router';
|
import { Routes } from './router';
|
||||||
import { RequestId, ErrorHandler } from './middleware';
|
import { RequestId, ErrorHandler, NotFoundHandler } from './middleware';
|
||||||
import { Services } from '../../app';
|
import { Services } from '../../app';
|
||||||
import { MongoConfig, ServerConfig } from '../../config';
|
import { MongoConfig, ServerConfig } from '../../config';
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import MongoStore from 'connect-mongo';
|
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
private app: Express;
|
private app: Express;
|
||||||
|
private server: http.Server;
|
||||||
private config: ServerConfig;
|
private config: ServerConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -20,6 +23,7 @@ class Server {
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
this.app = express();
|
this.app = express();
|
||||||
|
this.app.disable('x-powered-by');
|
||||||
this.app.use(express.json());
|
this.app.use(express.json());
|
||||||
this.app.use(
|
this.app.use(
|
||||||
cors({
|
cors({
|
||||||
@ -28,7 +32,6 @@ class Server {
|
|||||||
origin: this.config.origin,
|
origin: this.config.origin,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.app.use(RequestId());
|
|
||||||
this.app.use(
|
this.app.use(
|
||||||
session({
|
session({
|
||||||
proxy: process.env.NODE_ENV === 'production',
|
proxy: process.env.NODE_ENV === 'production',
|
||||||
@ -48,12 +51,25 @@ class Server {
|
|||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
this.app.use(RequestId());
|
||||||
this.app.use(Routes(services));
|
this.app.use(Routes(services));
|
||||||
|
this.app.use(NotFoundHandler());
|
||||||
this.app.use(ErrorHandler());
|
this.app.use(ErrorHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
start(func: () => void): void {
|
async start(): Promise<void> {
|
||||||
this.app.listen(this.config.port, func);
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
this.server = this.app.listen(this.config.port, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(): Promise<void> {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
this.server.close((error) => {
|
||||||
|
if (error) reject(error);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,86 +1,182 @@
|
|||||||
import { RequestHandler, Router } from 'express';
|
import { RequestHandler, Router } from 'express';
|
||||||
import { Services } from '../../app';
|
import { Services } from '../../app';
|
||||||
import { CreateUser, ReadUser, LoginUser } from '../../functions/user';
|
import {
|
||||||
|
CreateUser,
|
||||||
|
ReadUser,
|
||||||
|
LoginUser,
|
||||||
|
UpdateUser,
|
||||||
|
ListUsers,
|
||||||
|
AddCardToUser,
|
||||||
|
} from '../../functions/user';
|
||||||
import {
|
import {
|
||||||
LoginUserSchema,
|
LoginUserSchema,
|
||||||
CreateUserSchema,
|
CreateUserSchema,
|
||||||
ReadUserSchema,
|
ReadUserSchema,
|
||||||
LogoutUserSchema,
|
LogoutUserSchema,
|
||||||
|
UpdateUserSchema,
|
||||||
|
ListUsersSchema,
|
||||||
|
AddCardToUserSchema,
|
||||||
|
RemoveCardFromUserSchema,
|
||||||
} from './schema/user';
|
} from './schema/user';
|
||||||
import { CheckPermissions, getId, SchemaValidator } from './middleware';
|
import { CheckPermissions, getRequestId, ValidateSchema } from './middleware';
|
||||||
|
|
||||||
function LoginHandler(services: Services): RequestHandler {
|
function LoginUserHandler(services: Services): RequestHandler {
|
||||||
const login = LoginUser(services);
|
const login = LoginUser(services);
|
||||||
|
|
||||||
return async (req, res, next) => {
|
return async (req, res, next) => {
|
||||||
const user = await login(getId(req), req.body);
|
try {
|
||||||
|
const user = await login(getRequestId(req), req.body);
|
||||||
user ? (req.session.user = user) : (req.session.user = null);
|
user ? (req.session.user = user) : (req.session.user = null);
|
||||||
user
|
res.status(200).send(user);
|
||||||
? res.status(200).send(user)
|
} catch (error) {
|
||||||
: next({ status: 401, message: 'wrong username or password' });
|
next({ status: 401, message: 'wrong username or password' });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function LogoutHandler(services: Services): RequestHandler {
|
function LogoutUserHandler(services: Services): RequestHandler {
|
||||||
return async (req, res, next) => {
|
return async (req, res, next) => {
|
||||||
if (req.session.user) {
|
if (req.session.user) {
|
||||||
req.session.user = null;
|
req.session.user = null;
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
} else {
|
} else {
|
||||||
next({ status: 401, message: 'not logged in' });
|
next({ status: 401, message: 'Not Logged In' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateHandler(services: Services): RequestHandler {
|
function CreateUserHandler(services: Services): RequestHandler {
|
||||||
const createUser = CreateUser(services);
|
const createUser = CreateUser(services);
|
||||||
|
|
||||||
return async (req, res, next) => {
|
return async (req, res, next) => {
|
||||||
const user = await createUser(getId(req), req.body);
|
try {
|
||||||
user ? res.status(201).send(user) : next();
|
const user = await createUser(getRequestId(req), req.body);
|
||||||
|
res.status(201).send(user);
|
||||||
|
} catch (error) {
|
||||||
|
next({ status: 409, message: 'User Already Exists' });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReadHandler(services: Services): RequestHandler {
|
function ReadUserHandler(services: Services): RequestHandler {
|
||||||
const readUser = ReadUser(services);
|
const readUser = ReadUser(services);
|
||||||
|
|
||||||
return async (req, res, next) => {
|
return async (req, res, next) => {
|
||||||
const user = await readUser(getId(req), req.params.uuid);
|
try {
|
||||||
user
|
const user = await readUser(getRequestId(req), req.params.uuid);
|
||||||
? res.status(200).send(user)
|
res.status(200).send(user);
|
||||||
: next({ status: 404, message: 'user not found' });
|
} catch (error) {
|
||||||
|
next({ status: 404, message: 'User Not Found' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function UpdateUserHandler(services: Services): RequestHandler {
|
||||||
|
const updateUser = UpdateUser(services);
|
||||||
|
|
||||||
|
return async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const user = await updateUser(
|
||||||
|
getRequestId(req),
|
||||||
|
req.params.uuid,
|
||||||
|
req.body,
|
||||||
|
);
|
||||||
|
res.status(200).send(user);
|
||||||
|
} catch (error) {
|
||||||
|
next({ status: 404, message: 'User Not Found' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ListUserHandler(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' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddCardToUserHandler(services: Services): RequestHandler {
|
||||||
|
const addCardToUser = AddCardToUser(services);
|
||||||
|
|
||||||
|
return async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const user = await addCardToUser(
|
||||||
|
getRequestId(req),
|
||||||
|
req.params.uuid,
|
||||||
|
req.body.cardUuid,
|
||||||
|
);
|
||||||
|
res.status(200).send(user);
|
||||||
|
} catch (error) {
|
||||||
|
next({ message: 'Unknown Error' });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Routes(services: Services) {
|
export function Routes(services: Services) {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
//User
|
||||||
router.post(
|
router.post(
|
||||||
'/login',
|
'/login',
|
||||||
LoginUserSchema(),
|
LoginUserSchema(),
|
||||||
SchemaValidator(),
|
ValidateSchema(),
|
||||||
LoginHandler(services),
|
LoginUserHandler(services),
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/logout',
|
'/logout',
|
||||||
LogoutUserSchema(),
|
LogoutUserSchema(),
|
||||||
SchemaValidator(),
|
ValidateSchema(),
|
||||||
LogoutHandler(services),
|
LogoutUserHandler(services),
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/read/:uuid',
|
'/read/:uuid',
|
||||||
ReadUserSchema(),
|
|
||||||
SchemaValidator(),
|
|
||||||
CheckPermissions(),
|
CheckPermissions(),
|
||||||
ReadHandler(services),
|
ReadUserSchema(),
|
||||||
|
ValidateSchema(),
|
||||||
|
ReadUserHandler(services),
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/create',
|
'/create',
|
||||||
CreateUserSchema(),
|
|
||||||
SchemaValidator(),
|
|
||||||
CheckPermissions(),
|
CheckPermissions(),
|
||||||
CreateHandler(services),
|
CreateUserSchema(),
|
||||||
|
ValidateSchema(),
|
||||||
|
CreateUserHandler(services),
|
||||||
|
);
|
||||||
|
router.patch(
|
||||||
|
'/update/:uuid',
|
||||||
|
CheckPermissions(),
|
||||||
|
UpdateUserSchema(),
|
||||||
|
ValidateSchema(),
|
||||||
|
UpdateUserHandler(services),
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
'/list',
|
||||||
|
CheckPermissions(),
|
||||||
|
ListUsersSchema(),
|
||||||
|
ValidateSchema(),
|
||||||
|
ListUserHandler(services),
|
||||||
|
);
|
||||||
|
|
||||||
|
//Alteration
|
||||||
|
router.post(
|
||||||
|
'/addCard/:uuid',
|
||||||
|
CheckPermissions(),
|
||||||
|
AddCardToUserSchema(),
|
||||||
|
ValidateSchema(),
|
||||||
|
AddCardToUserHandler(services),
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/removeCard/:uuid',
|
||||||
|
CheckPermissions(),
|
||||||
|
RemoveCardFromUserSchema(),
|
||||||
|
ValidateSchema(),
|
||||||
|
//RemoveCardFromUserHandler(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
|
74
api/src/framework/mongo/card.ts
Normal file
74
api/src/framework/mongo/card.ts
Normal 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;
|
@ -1,8 +1,8 @@
|
|||||||
import { Collection, Db } from 'mongodb';
|
import { Collection, Db } from 'mongodb';
|
||||||
import { LoginUserBody } from '../../../../core/src/user';
|
import { Card, CardInfo, LoginUserBody, UpdateUserBody, UserInfo } from '@core';
|
||||||
import { User, UserWithPassword } from '../../entities/user';
|
import { User, UserWithPassword } from '../../entities/user';
|
||||||
import log from '../../functions/logger';
|
import { log } from '../../functions/logger';
|
||||||
import { generateHash } from '../../functions/password';
|
import { getHashedPassword } from '../../functions/password';
|
||||||
|
|
||||||
class UserModel {
|
class UserModel {
|
||||||
private collection: Collection<User>;
|
private collection: Collection<User>;
|
||||||
@ -11,42 +11,136 @@ class UserModel {
|
|||||||
this.collection = db.collection<User>('users');
|
this.collection = db.collection<User>('users');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async login(tracker: string, data: LoginUserBody) {
|
public async login(tracker: string, userInfo: LoginUserBody): Promise<User> {
|
||||||
const checkUser = await this.collection.findOne({
|
const checkUser = await this.collection.findOne({
|
||||||
username: data.username,
|
username: userInfo.username,
|
||||||
});
|
});
|
||||||
if (checkUser === null) {
|
if (!checkUser) {
|
||||||
log(tracker, 'User Not Found');
|
log(tracker, 'User Not Found');
|
||||||
return null;
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.collection.findOne({
|
const userDocument = await this.collection.findOne({
|
||||||
uuid: checkUser.uuid,
|
uuid: checkUser.uuid,
|
||||||
password: generateHash(checkUser.uuid, data.password),
|
password: getHashedPassword(checkUser.uuid, userInfo.password),
|
||||||
});
|
});
|
||||||
if (user === null) {
|
if (!userDocument) {
|
||||||
log(tracker, 'Wrong Password');
|
log(tracker, 'Wrong Password');
|
||||||
return null;
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user = new User(userDocument);
|
||||||
log(tracker, 'LOG IN', user);
|
log(tracker, 'LOG IN', user);
|
||||||
return new User(user);
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async create(tracker: string, user: UserWithPassword) {
|
public async create(
|
||||||
await this.collection.insertOne(user);
|
tracker: string,
|
||||||
|
userInfo: UserWithPassword,
|
||||||
|
): Promise<User> {
|
||||||
|
const checkUser = await this.collection.findOne({
|
||||||
|
username: userInfo.username,
|
||||||
|
});
|
||||||
|
if (checkUser) {
|
||||||
|
log(tracker, 'User Already Exists');
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.collection.insertOne(userInfo);
|
||||||
|
|
||||||
|
const user = await this.read(tracker, userInfo.uuid);
|
||||||
log(tracker, 'CREATE USER', user);
|
log(tracker, 'CREATE USER', user);
|
||||||
return this.read(tracker, user.uuid);
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async read(tracker: string, uuid: string) {
|
public async read(tracker: string, uuid: string): Promise<User> {
|
||||||
const user = await this.collection.findOne({ uuid });
|
const [userDocument] = await this.collection.aggregate<UserInfo>([
|
||||||
if (user === null) {
|
{
|
||||||
log(tracker, 'User Not Found');
|
$match:{
|
||||||
return null;
|
uuid
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "cards",
|
||||||
|
localField: "cards",
|
||||||
|
foreignField: "_id",
|
||||||
|
as: "cards"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]).toArray();
|
||||||
|
console.log(JSON.stringify(userDocument));
|
||||||
|
if (!userDocument) {
|
||||||
|
log(tracker, 'User Not Found');
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = new User(userDocument);
|
||||||
log(tracker, 'READ USER', user);
|
log(tracker, 'READ USER', user);
|
||||||
return new User(user);
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(
|
||||||
|
tracker: string,
|
||||||
|
uuid: string,
|
||||||
|
userInfo: UpdateUserBody,
|
||||||
|
): Promise<User> {
|
||||||
|
const checkUser = await this.collection.findOne({ uuid });
|
||||||
|
if (!checkUser) {
|
||||||
|
log(tracker, 'User Does Not Exist');
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userInfo.password)
|
||||||
|
userInfo.password = getHashedPassword(uuid, userInfo.password);
|
||||||
|
|
||||||
|
await this.collection.updateOne(
|
||||||
|
{ uuid },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
...userInfo,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = await this.read(tracker, uuid);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addCard(
|
||||||
|
tracker: string,
|
||||||
|
uuid: string,
|
||||||
|
card: Card,
|
||||||
|
): Promise<User> {
|
||||||
|
const checkUser = await this.collection.findOne({ uuid });
|
||||||
|
if (!checkUser) {
|
||||||
|
log(tracker, 'User Does Not Exist');
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.collection.updateOne(
|
||||||
|
{ uuid },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
$addToSet: {
|
||||||
|
cards: card._id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = await this.read(tracker, uuid);
|
||||||
|
log(tracker, 'ADD CARD TO USER', user, card);
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
api/src/functions/card.ts
Normal file
36
api/src/functions/card.ts
Normal 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());
|
||||||
|
};
|
||||||
|
}
|
@ -1,11 +1,10 @@
|
|||||||
export function log(tracker: string, ...message: unknown[]) {
|
export function log(tracker: string, ...message: unknown[]) {
|
||||||
|
if (message)
|
||||||
message.forEach((obj, index) => {
|
message.forEach((obj, index) => {
|
||||||
try {
|
try {
|
||||||
message[index] = JSON.stringify(obj);
|
message[index] = JSON.stringify(obj);
|
||||||
} catch {}
|
} catch {}
|
||||||
});
|
});
|
||||||
|
|
||||||
tracker ? console.log(`[${tracker}]`, ...message) : console.log(...message);
|
message ? console.log(`[${tracker}]`, ...message) : console.log(tracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default log;
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
|
export function getHashedPassword(uuid: string, password: string) {
|
||||||
|
return generateHash(uuid, password);
|
||||||
|
}
|
||||||
|
|
||||||
export function generateHash(...values: string[]) {
|
export function generateHash(...values: string[]) {
|
||||||
return createHash('sha256').update(values.join('')).digest('hex');
|
return createHash('sha256').update(values.join('')).digest('hex');
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,71 @@
|
|||||||
import { CreateUserBody, LoginUserBody, UserInfo } from '@core';
|
import { CreateUserBody, LoginUserBody, UserInfo, UpdateUserBody } from '@core';
|
||||||
import { Services } from '../app';
|
import { Services } from '../app';
|
||||||
import { UserWithPassword } from '../entities/user';
|
import { UserWithPassword } from '../entities/user';
|
||||||
|
|
||||||
export function LoginUser(
|
export function LoginUser(
|
||||||
services: Services,
|
services: Services,
|
||||||
): (tracker: string, raw: LoginUserBody) => Promise<UserInfo | null> {
|
): (tracker: string, raw: LoginUserBody) => Promise<UserInfo> {
|
||||||
const { userModel } = services;
|
const { userModel } = services;
|
||||||
|
|
||||||
return async (tracker, raw) => {
|
return async (tracker, raw) => {
|
||||||
const user = await userModel.login(tracker, raw);
|
const user = await userModel.login(tracker, raw);
|
||||||
return user ? user.Info() : null;
|
return user.Info();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CreateUser(
|
export function CreateUser(
|
||||||
services: Services,
|
services: Services,
|
||||||
): (tracker: string, raw: CreateUserBody) => Promise<UserInfo | null> {
|
): (tracker: string, raw: CreateUserBody) => Promise<UserInfo> {
|
||||||
const { userModel } = services;
|
const { userModel } = services;
|
||||||
|
|
||||||
return async (tracker, raw) => {
|
return async (tracker, raw) => {
|
||||||
const user = await userModel.create(tracker, new UserWithPassword(raw));
|
const user = await userModel.create(tracker, new UserWithPassword(raw));
|
||||||
return user ? user.Info() : null;
|
return user.Info();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReadUser(
|
export function ReadUser(
|
||||||
services: Services,
|
services: Services,
|
||||||
): (tracker: string, uuid: string) => Promise<UserInfo | null> {
|
): (tracker: string, uuid: string) => Promise<UserInfo> {
|
||||||
const { userModel } = services;
|
const { userModel } = services;
|
||||||
|
|
||||||
return async (tracker, uuid) => {
|
return async (tracker, uuid) => {
|
||||||
const user = await userModel.read(tracker, uuid);
|
const user = await userModel.read(tracker, uuid);
|
||||||
return user ? user.Info() : null;
|
return user.Info();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UpdateUser(
|
||||||
|
services: Services,
|
||||||
|
): (tracker: string, uuid: string, raw: UpdateUserBody) => Promise<UserInfo> {
|
||||||
|
const { userModel } = services;
|
||||||
|
|
||||||
|
return async (tracker, uuid, raw) => {
|
||||||
|
const user = await userModel.update(tracker, uuid, raw);
|
||||||
|
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());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddCardToUser(
|
||||||
|
services: Services,
|
||||||
|
): (tracker: string, uuid: string, cardUuid: string) => Promise<UserInfo> {
|
||||||
|
const { userModel, cardModel } = services;
|
||||||
|
|
||||||
|
return async (tracker, uuid, cardUuid) => {
|
||||||
|
const card = await cardModel.read(tracker, cardUuid);
|
||||||
|
|
||||||
|
const user = await userModel.addCard(tracker, uuid, card);
|
||||||
|
return user.Info();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
2568
core/package-lock.json
generated
2568
core/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.8.4",
|
"@types/node": "^18.8.4",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
"mongodb": "^4.10.0",
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
core/src/alteration.ts
Normal file
12
core/src/alteration.ts
Normal 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
40
core/src/card.ts
Normal 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'>>;
|
@ -1,4 +1,7 @@
|
|||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
export type EntityInfo = {
|
export type EntityInfo = {
|
||||||
|
_id: ObjectId;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
|
export * from './alteration';
|
||||||
|
export * from './card';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './entity';
|
export * from './entity';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { EntityInfo } from './entity';
|
import { EntityInfo } from './entity';
|
||||||
|
import { CardInfo } from './card';
|
||||||
|
|
||||||
export enum UserRoles {
|
export enum UserRoles {
|
||||||
ADMIN = 'ADMIN',
|
ADMIN = 'ADMIN',
|
||||||
@ -9,6 +10,7 @@ export type UserInfo = {
|
|||||||
uuid: string;
|
uuid: string;
|
||||||
username: string;
|
username: string;
|
||||||
role: keyof typeof UserRoles;
|
role: keyof typeof UserRoles;
|
||||||
|
cards: CardInfo[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type User = UserInfo & EntityInfo;
|
export type User = UserInfo & EntityInfo;
|
||||||
@ -29,6 +31,8 @@ export type CreateUserBody = {
|
|||||||
role: keyof typeof UserRoles;
|
role: keyof typeof UserRoles;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UpdateUserBody = Partial<Omit<UserInfoWithPassword, 'uuid'>>;
|
||||||
|
|
||||||
export type LoginUserBody = {
|
export type LoginUserBody = {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
0
scripts/deploy.sh
Executable file → Normal file
0
scripts/deploy.sh
Executable file → Normal file
26
scripts/tools.py
Executable file → Normal file
26
scripts/tools.py
Executable file → Normal file
@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
import os
|
from os import chdir, path
|
||||||
import sys
|
from sys import argv
|
||||||
|
from subprocess import run, PIPE
|
||||||
|
from multiprocessing import Process
|
||||||
|
|
||||||
apps = ['core', 'api', 'www']
|
apps = ['core', 'api', 'www']
|
||||||
commands = {
|
commands = {
|
||||||
@ -14,19 +16,29 @@ def print_commands():
|
|||||||
print('Available commands:', [c for c in commands])
|
print('Available commands:', [c for c in commands])
|
||||||
|
|
||||||
|
|
||||||
|
def run_command_in_app(app: str, command: str):
|
||||||
|
chdir(app)
|
||||||
|
result = run(command.split(' '), stdout=PIPE, stderr=PIPE, text=True)
|
||||||
|
|
||||||
|
status = 'DONE' if result.returncode == 0 else 'ERROR'
|
||||||
|
print('%s:\t%s' % (app, status))
|
||||||
|
if status == 'ERROR':
|
||||||
|
print(result.stdout, result.stderr)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) < 2:
|
if len(argv) < 2:
|
||||||
print_commands()
|
print_commands()
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
cmd = sys.argv[1]
|
cmd = argv[1]
|
||||||
if cmd not in commands:
|
if cmd not in commands:
|
||||||
print('Command \'%s\' not available' % cmd)
|
print('Command \'%s\' not available' % cmd)
|
||||||
print_commands()
|
print_commands()
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
chdir(path.join(path.dirname(path.realpath(__file__)), '..'))
|
||||||
|
|
||||||
print('Running \'%s\' on %d apps: %s' % (commands[cmd], len(apps), apps))
|
print('Running \'%s\' on %d apps: %s' % (commands[cmd], len(apps), apps))
|
||||||
for app in apps:
|
for app in apps:
|
||||||
os.chdir(app)
|
Process(target=run_command_in_app, args=(app, commands[cmd])).start()
|
||||||
os.system(commands[cmd])
|
|
||||||
os.chdir('..')
|
|
||||||
|
31
www/package-lock.json
generated
31
www/package-lock.json
generated
@ -22,6 +22,7 @@
|
|||||||
"@rollup/plugin-typescript": "^8.0.0",
|
"@rollup/plugin-typescript": "^8.0.0",
|
||||||
"@tsconfig/svelte": "^2.0.0",
|
"@tsconfig/svelte": "^2.0.0",
|
||||||
"@types/three": "^0.144.0",
|
"@types/three": "^0.144.0",
|
||||||
|
"@types/w3c-image-capture": "^1.0.7",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"prettier-plugin-svelte": "^2.8.0",
|
"prettier-plugin-svelte": "^2.8.0",
|
||||||
"rollup": "^2.3.4",
|
"rollup": "^2.3.4",
|
||||||
@ -353,6 +354,21 @@
|
|||||||
"@types/webxr": "*"
|
"@types/webxr": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/w3c-image-capture": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/w3c-image-capture/-/w3c-image-capture-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-BNmjJT+yjwVzB6N5kf1/m9YZMF//e3JAcII+TIuhRkALn7UD9xZX0R1azVGqqyYuQKbQ6UskC+zSQctnsk9zHg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/webrtc": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/webrtc": {
|
||||||
|
"version": "0.0.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.33.tgz",
|
||||||
|
"integrity": "sha512-xjN6BelzkY3lzXjIjXGqJVDS6XDleEsvp1bVIyNccXCcMoTH3wvUXFew4/qflwJdNqjmq98Zc5VcALV+XBKBvg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/webxr": {
|
"node_modules/@types/webxr": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
||||||
@ -1986,6 +2002,21 @@
|
|||||||
"@types/webxr": "*"
|
"@types/webxr": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/w3c-image-capture": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/w3c-image-capture/-/w3c-image-capture-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-BNmjJT+yjwVzB6N5kf1/m9YZMF//e3JAcII+TIuhRkALn7UD9xZX0R1azVGqqyYuQKbQ6UskC+zSQctnsk9zHg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/webrtc": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/webrtc": {
|
||||||
|
"version": "0.0.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.33.tgz",
|
||||||
|
"integrity": "sha512-xjN6BelzkY3lzXjIjXGqJVDS6XDleEsvp1bVIyNccXCcMoTH3wvUXFew4/qflwJdNqjmq98Zc5VcALV+XBKBvg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/webxr": {
|
"@types/webxr": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"author": "Yanis Rigaudeau - Axel Barault",
|
"author": "Yanis Rigaudeau - Axel Barault",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "rollup -c --failAfterWarnings",
|
||||||
"dev": "rollup -c -w",
|
"dev": "rollup -c -w",
|
||||||
"start": "sirv public --no-clear --host --single",
|
"start": "sirv public --no-clear --host --single",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
@ -18,6 +18,7 @@
|
|||||||
"@rollup/plugin-typescript": "^8.0.0",
|
"@rollup/plugin-typescript": "^8.0.0",
|
||||||
"@tsconfig/svelte": "^2.0.0",
|
"@tsconfig/svelte": "^2.0.0",
|
||||||
"@types/three": "^0.144.0",
|
"@types/three": "^0.144.0",
|
||||||
|
"@types/w3c-image-capture": "^1.0.7",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"prettier-plugin-svelte": "^2.8.0",
|
"prettier-plugin-svelte": "^2.8.0",
|
||||||
"rollup": "^2.3.4",
|
"rollup": "^2.3.4",
|
||||||
|
@ -1,15 +1,29 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import 'papercss/dist/paper.min.css';
|
import 'papercss/dist/paper.min.css';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import { Router, Route } from 'svelte-navigator';
|
import { Router, Route } from 'svelte-navigator';
|
||||||
|
import { currentUser } from './store/user';
|
||||||
import Test3D from './pages/test3D.svelte';
|
import { read } from './functions/user';
|
||||||
|
import ViewAR from './pages/ViewAR.svelte';
|
||||||
import Login from './pages/Login.svelte';
|
import Login from './pages/Login.svelte';
|
||||||
import Profile from './pages/Profile.svelte';
|
import Profile from './pages/Profile.svelte';
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if ($currentUser) {
|
||||||
|
try {
|
||||||
|
const user = await read($currentUser.uuid);
|
||||||
|
currentUser.set(user);
|
||||||
|
} catch (error) {
|
||||||
|
currentUser.set(null);
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Router>
|
<Router>
|
||||||
<Route path="/">
|
<Route path="/">
|
||||||
<Test3D />
|
<ViewAR />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/login">
|
<Route path="/login">
|
||||||
<Login />
|
<Login />
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import NavBar from '../components/NavBar.svelte';
|
|
||||||
import { beforeUpdate, afterUpdate, onMount, onDestroy } from 'svelte';
|
import { beforeUpdate, afterUpdate, onMount, onDestroy } from 'svelte';
|
||||||
import * as Three from 'three';
|
import * as Three from 'three';
|
||||||
|
|
||||||
let container: HTMLElement;
|
export let width: number;
|
||||||
let canvas: HTMLCanvasElement;
|
export let height: number;
|
||||||
let width: number;
|
|
||||||
let height: number;
|
|
||||||
|
|
||||||
//#region variables
|
//#region variables
|
||||||
//let innerWidth: number = 0;
|
let container: HTMLElement;
|
||||||
//let innerHeight: number = 0;
|
let canvas: HTMLCanvasElement;
|
||||||
let maxInnerWidth: number = 0;
|
let maxInnerWidth: number = 0;
|
||||||
let maxInnerHeight: number = 0;
|
let maxInnerHeight: number = 0;
|
||||||
|
|
||||||
@ -18,6 +15,21 @@
|
|||||||
let focalLength: number = 540 / Math.atan((70 * Math.PI) / 180);
|
let focalLength: number = 540 / Math.atan((70 * Math.PI) / 180);
|
||||||
let fov = 75;
|
let fov = 75;
|
||||||
//let baseHorizontalFOV: number = 2.0*Math.atan(baseAspectRation*Math.tan(baseVerticalFOV*0.5));
|
//let baseHorizontalFOV: number = 2.0*Math.atan(baseAspectRation*Math.tan(baseVerticalFOV*0.5));
|
||||||
|
|
||||||
|
let scene: Three.Scene = new Three.Scene();
|
||||||
|
let camera: Three.PerspectiveCamera = new Three.PerspectiveCamera(
|
||||||
|
75,
|
||||||
|
1,
|
||||||
|
0.1,
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
let renderer: Three.WebGLRenderer = new Three.WebGLRenderer();
|
||||||
|
let box: Three.BoxGeometry = new Three.BoxGeometry(1, 1, 1);
|
||||||
|
let mat: Three.MeshBasicMaterial = new Three.MeshBasicMaterial({
|
||||||
|
color: 0x00ff00,
|
||||||
|
});
|
||||||
|
let cube = new Three.Mesh(box, mat);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
//#region functions
|
//#region functions
|
||||||
@ -50,22 +62,6 @@
|
|||||||
resetMaxDimensions();
|
resetMaxDimensions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//#endregion
|
|
||||||
|
|
||||||
let scene: Three.Scene = new Three.Scene();
|
|
||||||
let camera: Three.PerspectiveCamera = new Three.PerspectiveCamera(
|
|
||||||
75,
|
|
||||||
1,
|
|
||||||
0.1,
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
let renderer: Three.WebGLRenderer = new Three.WebGLRenderer();
|
|
||||||
let box: Three.BoxGeometry = new Three.BoxGeometry(1, 1, 1);
|
|
||||||
let mat: Three.MeshBasicMaterial = new Three.MeshBasicMaterial({
|
|
||||||
color: 0x00ff00,
|
|
||||||
});
|
|
||||||
let cube = new Three.Mesh(box, mat);
|
|
||||||
|
|
||||||
function animate() {
|
function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
@ -75,6 +71,7 @@
|
|||||||
|
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
canvas = container.appendChild(renderer.domElement);
|
canvas = container.appendChild(renderer.domElement);
|
||||||
@ -96,7 +93,7 @@
|
|||||||
camera.setViewOffset(width, height, 0, 0, width, height);
|
camera.setViewOffset(width, height, 0, 0, width, height);
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
|
|
||||||
//renderer.setClearColor(0x000000, (innerHeight/(maxInnerHeight))/2 + 0.5);
|
renderer.setClearColor(0x000000, 0.1);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterUpdate(() => {});
|
afterUpdate(() => {});
|
||||||
@ -106,17 +103,15 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="Container" bind:this={container} />
|
||||||
id="container"
|
|
||||||
bind:this={container}
|
|
||||||
bind:clientWidth={width}
|
|
||||||
bind:clientHeight={height}
|
|
||||||
/>
|
|
||||||
<NavBar />
|
|
||||||
|
|
||||||
<!--<svelte:window bind:innerWidth bind:innerHeight /> -->
|
|
||||||
<style>
|
<style>
|
||||||
#container {
|
.Container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
45
www/src/components/CameraHandler.svelte
Normal file
45
www/src/components/CameraHandler.svelte
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import CameraPreview from './CameraPreview.svelte';
|
||||||
|
import { getMediaStream, getImageCapture } from '../helpers/mediaHelper';
|
||||||
|
|
||||||
|
export let width: number;
|
||||||
|
export let height: number;
|
||||||
|
|
||||||
|
let container: HTMLElement;
|
||||||
|
|
||||||
|
let cameraWidth: number = 0;
|
||||||
|
let cameraHeight: number = 0;
|
||||||
|
|
||||||
|
let pictureUrl: string;
|
||||||
|
let imageCapture: ImageCapture;
|
||||||
|
let mediaStreamPromise: Promise<MediaStream>;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
mediaStreamPromise = getMediaStream({ video: true }).then((mediaStream) => {
|
||||||
|
imageCapture = getImageCapture(mediaStream);
|
||||||
|
return mediaStream;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="Container">
|
||||||
|
{#await mediaStreamPromise}
|
||||||
|
<h1>Waiting for camera....</h1>
|
||||||
|
{:then mediaStream}
|
||||||
|
<CameraPreview {mediaStream} {height} {width} />
|
||||||
|
{:catch error}
|
||||||
|
<h1>Error: {error.message}</h1>
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.Container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
24
www/src/components/CameraPreview.svelte
Normal file
24
www/src/components/CameraPreview.svelte
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let mediaStream: MediaStream;
|
||||||
|
export let width: number;
|
||||||
|
export let height: number;
|
||||||
|
|
||||||
|
function srcObject(node, stream) {
|
||||||
|
node.srcObject = stream;
|
||||||
|
return {
|
||||||
|
update(newStream) {
|
||||||
|
if (node.srcObject != newStream) {
|
||||||
|
node.srcObject = newStream;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<video
|
||||||
|
use:srcObject={mediaStream}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
/>
|
@ -4,18 +4,15 @@ enum Methods {
|
|||||||
'PUT' = 'PUT',
|
'PUT' = 'PUT',
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get<T>(route: string): Promise<T | null> {
|
export async function get<T>(route: string): Promise<T> {
|
||||||
return request<T>('GET', route);
|
return request<T>('GET', route);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post<T>(
|
export async function post<T>(route: string, data?: unknown): Promise<T> {
|
||||||
route: string,
|
|
||||||
data?: unknown,
|
|
||||||
): Promise<T | null> {
|
|
||||||
return request<T>('POST', route, data);
|
return request<T>('POST', route, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function put<T>(route: string, data: unknown): Promise<T | null> {
|
export async function put<T>(route: string, data: unknown): Promise<T> {
|
||||||
return request<T>('PUT', route, data);
|
return request<T>('PUT', route, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +20,7 @@ async function request<T>(
|
|||||||
method: keyof typeof Methods,
|
method: keyof typeof Methods,
|
||||||
route: string,
|
route: string,
|
||||||
data?: unknown,
|
data?: unknown,
|
||||||
): Promise<T | null> {
|
): Promise<T> {
|
||||||
const response = await fetch(`${process.env.APIURL}${route}`, {
|
const response = await fetch(`${process.env.APIURL}${route}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
@ -34,8 +31,11 @@ async function request<T>(
|
|||||||
body: data ? JSON.stringify(data) : null,
|
body: data ? JSON.stringify(data) : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(await response.json());
|
||||||
|
|
||||||
if (response.ok && response.status !== 204) {
|
if (response.ok && response.status !== 204) {
|
||||||
return response.json() as T;
|
return response.json() as T;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
return {} as T;
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
import type { CreateUserBody, LoginUserBody, UserInfo } from '@core';
|
import type { CreateUserBody, LoginUserBody, UserInfo } from '@core';
|
||||||
import { post, get } from './request';
|
import { post, get } from './request';
|
||||||
import { currentUser } from '../store/user';
|
|
||||||
|
|
||||||
export async function login(raw: LoginUserBody) {
|
export async function login(raw: LoginUserBody) {
|
||||||
const user = await post<UserInfo>('/user/login', raw);
|
return post<UserInfo>('/user/login', raw);
|
||||||
currentUser.set(user);
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logout() {
|
export async function logout() {
|
||||||
await post('/user/logout');
|
await post<void>('/user/logout');
|
||||||
currentUser.set(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function read(uuid: string) {
|
export async function read(uuid: string) {
|
||||||
const user = await get<UserInfo>(`/user/read/${uuid}`);
|
return get<UserInfo>(`/user/read/${uuid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(raw: CreateUserBody) {
|
export async function create(raw: CreateUserBody) {
|
||||||
const user = await post<UserInfo>('/user/create', raw);
|
return post<UserInfo>('/user/create', raw);
|
||||||
}
|
}
|
||||||
|
10
www/src/helpers/mediaHelper.ts
Normal file
10
www/src/helpers/mediaHelper.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
function getMediaStream(constraints = { video: true }) {
|
||||||
|
return navigator.mediaDevices.getUserMedia(constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImageCapture(mediaStream) {
|
||||||
|
const track = mediaStream.getVideoTracks()[0];
|
||||||
|
return new ImageCapture(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getMediaStream, getImageCapture };
|
@ -3,6 +3,7 @@
|
|||||||
import NavBar from '../components/NavBar.svelte';
|
import NavBar from '../components/NavBar.svelte';
|
||||||
import { Form, Input, Button } from 'spaper';
|
import { Form, Input, Button } from 'spaper';
|
||||||
import { login } from '../functions/user';
|
import { login } from '../functions/user';
|
||||||
|
import { currentUser } from '../store/user';
|
||||||
import { useNavigate } from 'svelte-navigator';
|
import { useNavigate } from 'svelte-navigator';
|
||||||
|
|
||||||
const user: LoginUserBody = {
|
const user: LoginUserBody = {
|
||||||
@ -13,10 +14,11 @@
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const loginHandler = async () => {
|
const loginHandler = async () => {
|
||||||
const result = await login(user);
|
try {
|
||||||
if (result) {
|
const res = await login(user);
|
||||||
|
currentUser.set(res);
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -39,7 +41,7 @@
|
|||||||
type="secondary"
|
type="secondary"
|
||||||
class="row sm-2 col-4"
|
class="row sm-2 col-4"
|
||||||
block
|
block
|
||||||
on:click={() => loginHandler()}>Sign in</Button
|
on:click={loginHandler}>Sign in</Button
|
||||||
>
|
>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,12 +8,16 @@
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const logoutHandler = async () => {
|
const logoutHandler = async () => {
|
||||||
|
try {
|
||||||
await logout();
|
await logout();
|
||||||
|
} finally {
|
||||||
|
currentUser.set(null);
|
||||||
navigate('/');
|
navigate('/');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button type="danger" block on:click={() => logoutHandler()}>Logout</Button>
|
<Button type="danger" block on:click={logoutHandler}>Logout</Button>
|
||||||
{$currentUser?.uuid}
|
{$currentUser?.uuid}
|
||||||
{$currentUser?.username}
|
{$currentUser?.username}
|
||||||
{$currentUser?.role}
|
{$currentUser?.role}
|
||||||
|
26
www/src/pages/ViewAR.svelte
Normal file
26
www/src/pages/ViewAR.svelte
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import View3D from '../components/3DHandler.svelte';
|
||||||
|
import Camera from '../components/CameraHandler.svelte';
|
||||||
|
import NavBar from '../components/NavBar.svelte';
|
||||||
|
|
||||||
|
let containerHeight: number;
|
||||||
|
let containerWidth: number;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="viewAR"
|
||||||
|
bind:clientHeight={containerHeight}
|
||||||
|
bind:clientWidth={containerWidth}
|
||||||
|
>
|
||||||
|
<Camera height={containerHeight} width={containerWidth} />
|
||||||
|
<View3D height={containerHeight} width={containerWidth} />
|
||||||
|
</div>
|
||||||
|
<NavBar />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.viewAR {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
Reference in New Issue
Block a user