Skip to content

Commit

Permalink
Merge branch 'dev' into feat/#7
Browse files Browse the repository at this point in the history
  • Loading branch information
reone19 authored Jun 23, 2022
2 parents 1206977 + 190cff4 commit ae17f6f
Show file tree
Hide file tree
Showing 17 changed files with 168 additions and 76 deletions.
2 changes: 1 addition & 1 deletion application/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * as quest from "application/Quest";
export * from "./Quest";
23 changes: 23 additions & 0 deletions core/Account/Avatar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Item } from "core";

export enum Race {
Egg,
Plant,
}

export class Avatar {
/**
* Level function is `50x ^ 2`.
*/
public get level(): number {
return this.totalExp == 0 ? 0 : Math.sqrt(this.totalExp / 50);
}

constructor(public race: Race, public totalExp: number) {}

public applyItem(...items: Item[]): void {
items.forEach((item) => {
item.takeEffect();
});
}
}
32 changes: 32 additions & 0 deletions core/Account/User.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IAccountRepository } from "infrastructure";
import { Avatar, Race } from "./Avatar";
import { Player, User } from "./User";

describe("User's functionality", () => {
test("password hashing", () => {
const cases = [
{ raw: "password", hash: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" },
{ raw: "cello", hash: "9bbf02efd82322aadc5d06c9bcf35bb4b0e3302ca158dc800407be1a4fea67e2" },
];

cases.forEach((c) => {
expect(User.hashPassword(c.raw)).toBe(c.hash.toLowerCase());
});
});
});

describe("Player's functionality", () => {
test("player level", () => {
[
{ exp: 0, lv: 0 },
{ exp: 1250, lv: 5 },
{ exp: 5000, lv: 10 },
].forEach(async (c) => {
const repoMock = jest.fn<Avatar, []>(() => new Avatar(Race.Egg, c.exp));
const p = await Player.New({ getAvatar: repoMock } as unknown as IAccountRepository, "", "");
const lv = p.level;
expect(repoMock).toBeCalledTimes(1);
expect(lv).toBe(c.lv);
});
});
});
81 changes: 81 additions & 0 deletions core/Account/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Avatar } from "core";
import crypto from "crypto";
import { IAccountRepository } from "infrastructure";

export abstract class User {
public password?: string;

constructor(public repo: IAccountRepository, public accountId: string, public email: string) {}

public async login(options: LoginOptions): Promise<boolean> {
if (this.password === undefined) {
this.password = await this.repo.getUserPassword(this.accountId);
}

let result: boolean = this.password === User.hashPassword(options.password);
let timestamp: number = Date.now();

if (result) {
this.repo.setLastLogin(timestamp);
} else {
this.repo.setLastLoginAttempt(timestamp);
}

return result;
}

public static hashPassword(password: string): string {
return crypto.createHash("sha256", {}).update(password).digest("hex");
}

public static register(repo: IAccountRepository, email: string, password: string): Promise<void> {
password = this.hashPassword(password);
return Promise.resolve(
repo.registerNewUser({ repo: repo, accountId: "", email: email, password: password } as User)
);
}

public async updatePassword(password: string): Promise<boolean> {
this.password = this.password ?? (await this.repo.getUserPassword(this.accountId));

password = User.hashPassword(password);

if (password === this.password) {
return false;
}

this.repo.updateUserPassword(this.accountId, password);
return true;
}

public upgradeToPlayer(avatar: Avatar): Player {
const p = new Player(this.repo, this.accountId, this.email);
p.avatar = avatar;
return p;
}
}

export class LoginOptions {
constructor(public email: string, public password: string) {}
}

export class Player extends User {
public avatar!: Avatar;

public get level(): number {
return this.avatar.level;
}

/**
* プレイヤーをインスタンス化する場合は`New`を使ってください。
*/
constructor(repo: IAccountRepository, accountId: string, email: string) {
super(repo, accountId, email);
}

public static async New(repo: IAccountRepository, accountId: string, email: string): Promise<Player> {
const p = new Player(repo, accountId, email);
p.avatar = await p.repo.getAvatar(p.accountId);
return p;
}
}
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion domain/Quest/Item.ts → core/Quest/Item.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Effect, EffectOption } from "domain/Quest";
import { Effect, EffectOption } from "core";

export abstract class Item {
public effect: Effect | null;
Expand Down
2 changes: 1 addition & 1 deletion domain/Quest/Quest.ts → core/Quest/Quest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Answer, Item } from "domain/Quest";
import { Answer, Item } from "core";

export abstract class Quest {
private solution: Answer;
Expand Down
6 changes: 6 additions & 0 deletions core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./Account/Avatar";
export * from "./Account/User";
export * from "./Quest/Answer";
export * from "./Quest/Effect";
export * from "./Quest/Item";
export * from "./Quest/Quest";
11 changes: 0 additions & 11 deletions domain/Account/Avatar.ts

This file was deleted.

41 changes: 0 additions & 41 deletions domain/Account/User.ts

This file was deleted.

2 changes: 0 additions & 2 deletions domain/Account/index.ts

This file was deleted.

4 changes: 0 additions & 4 deletions domain/Quest/index.ts

This file was deleted.

2 changes: 0 additions & 2 deletions domain/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * as domain from "./domain";
export * as domain from "./core";
export * as infrastructure from "./infrastructure";
export * as application from "./application";
32 changes: 21 additions & 11 deletions infrastructure/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import { Avatar, Player, User } from "domain/Account";
import { Answer, Item, Quest } from "domain/Quest";
import { Answer, Avatar, Item, Player, Quest, User } from "core";

export type Identifier = string | number;

export interface IQuestRepository {
getQuest: (id: Identifier) => Quest;
getAnswer: (id: Identifier) => Answer;
getItem: (id: Identifier) => Item;
getQuest: (id: Identifier) => Promise<Quest>;
getAnswer: (id: Identifier) => Promise<Answer>;
getItem: (id: Identifier) => Promise<Item>;
}

export interface IAccountRepository {
getUser(id: Identifier): User;
getPlayer(id: Identifier): Player;
getAvatar(id: Identifier): Avatar;
getUser(id: Identifier): Promise<User>;
getUserPassword(id: Identifier): Promise<string>;
getPlayer(id: Identifier): Promise<Player>;
/**
* プレイヤーのIDからアバターを取得する。
* @param player プレイヤーのID
*/
getAvatar(player: Identifier): Promise<Avatar>;
/**
* ユーザーのパスワードを更新する。
* @param user ユーザーのID
* @param password 新しいパスワードのハッシュ
*/
updateUserPassword(user: Identifier, password: string): Promise<void>;

setLastLoginAttempt(timestamp: number): void;
setLastLogin(timestamp: number): void;
registerNewUser(user: User): void;
setLastLoginAttempt(timestamp: number): Promise<void>;
setLastLogin(timestamp: number): Promise<void>;
registerNewUser(user: User): Promise<void>;
upgradeUserToPlayer(user: User, player: Player): Promise<void>;
unregisterUser(id: Identifier): Promise<void>;
unregisterPlayer(id: Identifier): Promise<void>;
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
"allowUnreachableCode": false,
"skipLibCheck": true
},
"include": ["application/**/*.ts", "domain/**/*.ts", "infrastructure/**/*.ts"]
"include": ["application/**/*.ts", "core/**/*.ts", "infrastructure/**/*.ts"]
}

0 comments on commit ae17f6f

Please sign in to comment.