Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/elct9620/GGJ2025
Browse files Browse the repository at this point in the history
  • Loading branch information
timcheng78 committed Jan 25, 2025
2 parents a369bde + 4f7e698 commit 5561b53
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/app/routes/email.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DestroyController } from "@controller/DestroyController";
import { NewGameController } from "@controller/NewGameController";
import { NpcJackController } from "@controller/NpcJackController";

interface Controller {
handle(userId: string, message: ForwardableEmailMessage): Promise<void>;
Expand All @@ -12,4 +13,5 @@ type Routes = {
export const routes: Routes = {
new: NewGameController,
destroy: DestroyController,
jack: NpcJackController,
};
38 changes: 38 additions & 0 deletions src/controller/NpcJackController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { inject, injectable } from "tsyringe-neo";

import { EmailController, EmailParams } from "./EmailController";
import { SESv2Client } from "@aws-sdk/client-sesv2";
import { SesEmailPresenter } from "@presenter/SesEmailPresenter";
import { KvCityRepository } from "@repository/KvCityRepository";
import { TaklWithJackUsecase } from "@usecase/TalkWithJackUsecase";

@injectable()
export class NpcJackController extends EmailController {
public static readonly SenderName: string = "Jack";

constructor(
@inject(SESv2Client) private readonly ses: SESv2Client,
@inject(KvCityRepository) private readonly cityRepository: KvCityRepository,
) {
super();
}

protected async onMessage(params: EmailParams): Promise<void> {
var sender = params.to;
if (!sender.includes("<")) {
sender = `${NpcJackController.SenderName} <${sender}>`;
}

const presenter = new SesEmailPresenter(this.ses, {
sender,
recipients: [params.from],
messageId: params.messageId,
references: params.references,
subject: params.subject,
});
const usecase = new TaklWithJackUsecase(presenter, this.cityRepository);

await usecase.execute(params.userId, params.body);
await presenter.render();
}
}
30 changes: 29 additions & 1 deletion src/entity/City.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
import { handle } from "hono/cloudflare-pages";
import { CityEvent, CityEventType, CityInitializedEvent } from "./CityEvent";

type EventHandler<T extends CityEvent> = (event: T) => void;

export class City {
constructor(public readonly id: string) {}
private _events: CityEvent[] = [];

constructor(public readonly id: string) {
this.apply(new CityInitializedEvent({}));
}

public apply(event: CityEvent): void {
const handlerName = `on${event.type}` as keyof this;
const handler = this[handlerName] as EventHandler<typeof event>;
if (!handler) {
throw new Error(`Missing event handler: ${String(handlerName)}`);
}

handler(event);
this._events.push(event);
}

public get events(): CityEvent[] {
return this._events.map((event) => ({ ...event }) as CityEvent);
}

private onCityInitializedEvent(event: CityInitializedEvent): void {
// Do nothing
}
}
23 changes: 23 additions & 0 deletions src/entity/CityEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export enum CityEventType {
CityInitializedEvent = "CityInitializedEvent",
}

abstract class Event {
constructor(
public readonly type: CityEventType,
public readonly _payload: Record<string, any>,
public readonly createdAt: Date = new Date(),
) {}

get payload(): Record<string, any> {
return { ...this._payload };
}
}

export class CityInitializedEvent extends Event {
constructor(payload: Record<string, any>) {
super(CityEventType.CityInitializedEvent, payload);
}
}

export type CityEvent = CityInitializedEvent;
39 changes: 36 additions & 3 deletions src/repository/KvCityRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@ import { type KVNamespace } from "@cloudflare/workers-types";
import { inject, injectable } from "tsyringe-neo";
import { CloudFlareKv } from "@app/container";
import { City } from "@entity/City";
import {
CityEvent,
CityEventType,
CityInitializedEvent,
} from "@entity/CityEvent";

type CitySchema = {};
type EventSchema = {
type: string;
payload: Record<string, any>;
createdAt: string;
};

type CitySchema = {
events: EventSchema[];
};

@injectable()
export class KvCityRepository {
Expand All @@ -20,17 +33,37 @@ export class KvCityRepository {
return null;
}

return new City(userId);
const city = new City(userId);
data.events.forEach((event) => {
city.apply(this.rebuildEvent(event));
});

return city;
}

async save(city: City): Promise<void> {
await this.kv.put(
`${KvCityRepository.Prefix}:${city.id}`,
JSON.stringify({}),
JSON.stringify({
events: city.events.map((event) => ({
type: event.type,
payload: event.payload,
createdAt: event.createdAt.toISOString(),
})),
}),
);
}

async destroy(userId: string): Promise<void> {
await this.kv.delete(`${KvCityRepository.Prefix}:${userId}`);
}

private rebuildEvent(event: EventSchema): CityEvent {
switch (event.type) {
case CityEventType.CityInitializedEvent:
return new CityInitializedEvent(event.payload);
default:
throw new Error(`Unknown event type: ${event.type}`);
}
}
}
13 changes: 13 additions & 0 deletions src/usecase/TalkWithJackUsecase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CityRepository, EmailPresenter } from "./interface";

export class TaklWithJackUsecase {
constructor(
private readonly presenter: EmailPresenter,
private readonly cityRepository: CityRepository,
) {}

async execute(userId: string, message: string): Promise<void> {
await this.cityRepository.destroy(userId);
this.presenter.addText("Hello, I'm Jack.");
}
}
9 changes: 9 additions & 0 deletions src/view/City.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,49 @@ import { css, cx } from "hono/css";
const pRelative = css`
position: relative;
`;
`;
const pAbsolute = css`
position: absolute;
`;
`;

const container = css`
display: flex;
width: 768px;
`;
`;
const shell = css`
bottom: 33%;
left: 40%;
`;
`;

const factory = css`
bottom: 46%;
left: 7%;
`;
left: 7%;
`;
const hospital = css`
bottom: 46%;
right: 5%;
`;
`;

const office = css`
bottom: 35%;
left: 0%;
`;
`;
const people = css`
bottom: 34%;
right: 0%;
`;
`;

export const City: FC<CitySnapshot> = ({ id }) => {
return (
Expand Down

0 comments on commit 5561b53

Please sign in to comment.