Skip to content

Commit

Permalink
Merge pull request #12 from halimath/refactor/backend-architecture
Browse files Browse the repository at this point in the history
Backend Architecture Refactoring
  • Loading branch information
halimath committed May 5, 2024
2 parents e5b66af + eef6c02 commit 9b756aa
Show file tree
Hide file tree
Showing 53 changed files with 4,815 additions and 2,284 deletions.
45 changes: 40 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- uses: actions/setup-node@v2
- uses: actions/setup-node@v4
with:
node-version: "20"

Expand All @@ -26,17 +26,52 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v4
with:
go-version: "1.22"

- name: Create dummy frontend
run: touch internal/boundary/public
run: touch internal/web/public
working-directory: backend

- name: Run tests
run: go test ./... -cover
working-directory: backend

e2etests:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: "3.12"

- name: Start app
run: docker compose up -d --build
working-directory: e2e-tests

- name: Run image
uses: abatilo/actions-poetry@v2
with:
poetry-version: "1.8.2"

- name: Install dependencies
run: poetry install
working-directory: e2e-tests

- name: Install playwright browsers
run: poetry run playwright install
working-directory: e2e-tests

- name: Run tests
run: poetry run pytest
working-directory: e2e-tests

- name: Stop app
run: docker compose down
working-directory: e2e-tests

4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ WORKDIR /backend

COPY ./backend/ ./

COPY --from=NODEJS /build/app/dist/ /backend/internal/boundary/public/
COPY --from=NODEJS /build/app/dist/ /backend/internal/web/public/

ENV CGO_ENABLED=0
RUN go build -ldflags "-X main.Version=${version} -X main.Commit=${commit}" .
RUN go build -ldflags "-X internal.Version=${version} -X internal.Commit=${commit}" .

FROM scratch

Expand Down
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"scripts": {
"start": "vite --host 0.0.0.0",
"build": "npm run generate-api-client && tsc && vite build",
"buildAndCopy": "npm run build && rm -rf ../backend/internal/web/public && cp -r dist ../backend/internal/web/public",
"preview": "vite preview",
"lint": "eslint .",
"test": "mocha --require ts-node/register test/**/*.test.ts",
Expand Down
2 changes: 1 addition & 1 deletion app/public/messages/de.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"ok": "OK",
"close": "Schließen",
"title": "Fate Core Rempte Table",
"title": "Fate Core Remote Table",
"home.createNewSession": "Eine neue Sitzung öffnen",
"home.createNewSession.prompt": "Wie soll die neue Sitzung heißen?",
"home.joinSession": "An einer Sitzung teilnehmen",
Expand Down
17 changes: 11 additions & 6 deletions app/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import * as wecco from "@weccoframework/core"
import { ApiClient, CreateCharacter, Session as SessionDto } from "../../generated"
import { ApiClient, Session as SessionDto } from "../../generated"
import { Message, ReplaceScene } from "../control"
import { Aspect, Gamemaster, Player, PlayerCharacter, Session } from "../models"

abstract class ApiBase {
private readonly interval: number

protected constructor(
protected readonly emit: wecco.MessageEmitter<Message>,
protected readonly apiClient: ApiClient,
readonly sessionId: string,
private readonly modelCtor: new (table: Session) => Gamemaster | PlayerCharacter,
protected readonly characterId?: string,
) {
this.requestUpdate()
setInterval(this.requestUpdate.bind(this), 5000)
this.requestUpdate()
this.interval = setInterval(this.requestUpdate.bind(this), 1000)
}

close() {
clearInterval(this.interval)
}

protected async requestUpdate() {
Expand Down Expand Up @@ -124,14 +130,13 @@ export class GamemasterApi extends ApiBase {
}

export class PlayerCharacterApi extends ApiBase {
static async joinGaim(emit: wecco.MessageEmitter<Message>, id: string, name: string): Promise<PlayerCharacterApi> {
static async joinGame(emit: wecco.MessageEmitter<Message>, id: string, name: string): Promise<PlayerCharacterApi> {
const apiClient = await createApiClient()

const characterId = await apiClient.session.createCharacter({
const characterId = await apiClient.session.joinSession({
id: id,
requestBody: {
name: name,
type: CreateCharacter.type.PC,
}
})

Expand Down
5 changes: 4 additions & 1 deletion app/src/control/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,19 @@ export class Controller {
return new Model(model.versionInfo, new Home(), new Notification(m("tableClosed.message")))

case "new-session":
this.api?.close()
this.api = await GamemasterApi.createSession(emit, message.title)
history.pushState(null, "", `/session/${this.api.sessionId}`)
break

case "rejoin-session":
this.api?.close()
this.api = await GamemasterApi.joinSession(emit, message.sessionId)
break

case "join-character":
this.api = await PlayerCharacterApi.joinGaim(emit, message.id, message.name)
this.api?.close()
this.api = await PlayerCharacterApi.joinGame(emit, message.id, message.name)
break

case "update-fate-points":
Expand Down
14 changes: 7 additions & 7 deletions app/src/views/components/skillcheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@ export const SkillCheck = wecco.define("fcrt-skillcheck", ({data, requestUpdate}
<div class="fate-icon text-4xl text-blue-700 flex items-center justify-around">OCAD</div>
<div class="flex items-center justify-around">
${[
button({ label: "+0", onClick: () => { data.result = Result.roll(0); requestUpdate() } }),
button({ label: "+1", onClick: () => { data.result = Result.roll(1); requestUpdate() } }),
button({ label: "+2", onClick: () => { data.result = Result.roll(2); requestUpdate() } }),
button({ label: "+3", onClick: () => { data.result = Result.roll(3); requestUpdate() } }),
button({ label: "+4", onClick: () => { data.result = Result.roll(4); requestUpdate() } }),
button({ label: "+5", onClick: () => { data.result = Result.roll(5); requestUpdate() } }),
button({ label: "+0", testId: "skill-check-0-btn", onClick: () => { data.result = Result.roll(0); requestUpdate() } }),
button({ label: "+1", testId: "skill-check-1-btn", onClick: () => { data.result = Result.roll(1); requestUpdate() } }),
button({ label: "+2", testId: "skill-check-2-btn", onClick: () => { data.result = Result.roll(2); requestUpdate() } }),
button({ label: "+3", testId: "skill-check-3-btn", onClick: () => { data.result = Result.roll(3); requestUpdate() } }),
button({ label: "+4", testId: "skill-check-4-btn", onClick: () => { data.result = Result.roll(4); requestUpdate() } }),
button({ label: "+5", testId: "skill-check-5-btn", onClick: () => { data.result = Result.roll(5); requestUpdate() } }),
]}
</div>
Expand All @@ -78,7 +78,7 @@ function resultView(result: Result): wecco.ElementUpdate {
const total = result.total === "below" ? -2 : (result.total === "above" ? 8 : result.total)

return wecco.html`
<div class="flex flex-row items-center justify-center text-blue-700">
<div class="flex flex-row items-center justify-center text-blue-700" data-testid="skill-check-result">
<span class="fate-icon text-xl lg:text-2xl">${result.rolls.map(r => (r == -1) ? "-" : ((r == 1) ? "+" : "0"))}</span>
<span class="text-lg mr-2">${rating(result.rating)}</span>
<span class="text-lg mr-2">=</span>
Expand Down
19 changes: 12 additions & 7 deletions app/src/views/scenes/gamemaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function gamemaster(versionInfo: VersionInfo, model: Gamemaster, emit: we
button({
label: wecco.html`<i class="material-icons">share</i>`,
onClick: share.bind(undefined, model),
testId: "join-session-link"
}),
]
})
Expand All @@ -27,16 +28,17 @@ function content(model: Gamemaster, emit: wecco.MessageEmitter<Message>): wecco.
<fcrt-skillcheck></fcrt-skillcheck>
<div class="grid grid-cols-1 lg:grid-cols-2 place-content-start">
<div class="flex flex-col">
<div class="flex flex-col" data-testid="aspects">
${model.session.aspects.map(aspect.bind(undefined, emit))}
<div class="flex justify-center ml-2 mr-2 mt-2">
${button({
label: wecco.html`<i class="material-icons">add</i> ${m("gamemaster.addAspect")}`,
onClick: addAspect.bind(null, emit, undefined),
testId: "add-aspect",
})}
</div>
</div>
<div class="flex flex-col">
<div class="flex flex-col" data-testid="players">
${model.session.players.map(player.bind(undefined, emit))}
</div>
</div>
Expand All @@ -54,15 +56,16 @@ function aspect(emit: wecco.MessageEmitter<Message>, aspect: Aspect): wecco.Elem

function player(emit: wecco.MessageEmitter<Message>, player: Player): wecco.ElementUpdate {
return card(wecco.html`
<h3 class="text-lg font-bold text-yellow-700">${player.name}</h3>
<h3 class="text-lg font-bold text-yellow-700" data-testid="name">${player.name}</h3>
<div class="grid grid-cols-2">
<div>
<div data-testid="aspects">
${player.aspects.map(aspect.bind(undefined, emit))}
<div class="flex justify-center">
${button({
label: m("gamemaster.addAspect"),
onClick: addAspect.bind(null, emit, player.id),
size: "s",
testId: "player-add-aspect",
})}
</div>
</div>
Expand All @@ -79,13 +82,15 @@ function fatePoints(fatePoints: number, onChange: (value: number) => void): wecc
color: "yellow",
size: "s",
disabled: fatePoints === 0,
testId: "dec-fate-points",
})}
<span class="text-3xl font-bold text-yellow-600">${fatePoints}</span>
<span class="text-3xl font-bold text-yellow-600" data-testid="fate-points">${fatePoints}</span>
${button({
label: "+",
onClick: onChange.bind(undefined, 1),
color: "yellow",
size: "s",
testId: "inc-fate-points",
})}
</div>`
}
Expand All @@ -107,8 +112,8 @@ function addAspect(emit: wecco.MessageEmitter<Message>, characterId?: string) {
modal({
title: m("gamemaster.addAspect"),
body: wecco.html`
<p>${m("gamemaster.addAspect.prompt")}</p>
<input type="text" @update=${bindNameInput}>
<label for="aspect-name">${m("gamemaster.addAspect.prompt")}</label>
<input type="text" id="aspect-name" data-testid="aspect-name" @update=${bindNameInput}>
`,
actions: [
{
Expand Down
15 changes: 9 additions & 6 deletions app/src/views/scenes/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ export function home(versionInfo: VersionInfo, model: Home, emit: wecco.MessageE
${button({
label: m("home.joinSession"),
onClick: joinSession.bind(null, emit, undefined),
testId: "join-session-btn",
})}
${button({
label: m("home.createNewSession"),
color: "yellow",
onClick: startNewSession.bind(null, emit),
testId: "create-session-btn",
})}
</div>
</div>`
Expand All @@ -48,8 +50,8 @@ function startNewSession(emit: wecco.MessageEmitter<Message>) {
modal({
title: m("home.createNewSession"),
body: wecco.html`
<p>${m("home.createNewSession.prompt")}</p>
<input type="text" @update=${bindTitleInput}>`,
<label for="session-title">${m("home.createNewSession.prompt")}</label>
<input id="session-title" data-testid="session-title" type="text" @update=${bindTitleInput}>`,
actions: [
{
label: m("ok"),
Expand Down Expand Up @@ -90,10 +92,11 @@ function joinSession(emit: wecco.MessageEmitter<Message>, urlOrId?: string) {
modal({
title: m("home.joinSession"),
body: wecco.html`
<p>${m("home.joinSession.promptId")}</p>
<input type="text" @update=${bindIdInput} value=${urlOrId ?? ""}>
<p>${m("home.joinSession.promptName")}</p>
<input type="text" @update=${bindNameInput}>
<label for="session-id">${m("home.joinSession.promptId")}</label>
<input id="session-id" data-testid="session-id" type="text" @update=${bindIdInput} value=${urlOrId ?? ""}>
<label for="player-name">${m("home.joinSession.promptName")}</label>
<input id="player-name" data-testid="player-name" type="text" @update=${bindNameInput}>
`,
actions: [
{
Expand Down
5 changes: 3 additions & 2 deletions app/src/views/scenes/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function content(player: PlayerCharacter, emit: wecco.MessageEmitter<Message>):
}

function aspects(table: Session): wecco.ElementUpdate {
return wecco.html`<div class="grid grid-cols-1">
return wecco.html`<div class="grid grid-cols-1" data-testid="aspects">
${table.aspects.map(a => aspect(a))}
${table.players.map(p => p.aspects.map(a => aspect(a, p)))}
</div>`
Expand All @@ -46,12 +46,13 @@ const fatePointActions = [
function fatePoints(fatePoints: number, emit: wecco.MessageEmitter<Message>): wecco.ElementUpdate {
return wecco.html`<div class="flex items-center justify-around">
<div class="flex items-center justify-center flex-col">
<span class="text-3xl font-bold text-yellow-600">${fatePoints}</span>
<span class="text-3xl font-bold text-yellow-600" data-testid="fate-points">${fatePoints}</span>
${button({
label: m(`player.spendFatePoint`),
onClick: () => emit(new SpendFatePoint()),
color: "yellow",
disabled: fatePoints === 0,
testId: "spend-fate-point",
})}
</div>
<ul class="text-gray-400">
Expand Down
4 changes: 2 additions & 2 deletions app/src/views/widgets/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function modal(opts: ModalOptions): Modal {
document.body.appendChild(modalElement)
wecco.updateElement(modalElement, wecco.html`
<div class="flex items-end justify-center lg:min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0 mt-24">
<div class="relative inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div class="relative inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" role="dialog" data-testid="modal">
<div class="bg-gray-100 px-4 py-3 sm:px-6 sm:flex"><h3>${opts.title ?? ""}</h3></div>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
Expand Down Expand Up @@ -117,5 +117,5 @@ function renderModalAction(a: ModalAction, modal: Modal): wecco.ElementUpdate {
break
}

return wecco.html`<button type="button" @click=${() => a.action(modal)} class=${classes}>${a.label}</button>`
return wecco.html`<button type="button" data-testid="modal-btn-${a.kind}" @click=${() => a.action(modal)} class=${classes}>${a.label}</button>`
}
10 changes: 6 additions & 4 deletions app/src/views/widgets/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function appShell(opts: AppShellOptions): wecco.ElementUpdate {
<header class="sticky top-0 z-30 w-full max-w-8xl mx-auto mb-2 flex-none flex bg-blue-900">
<div
class="flex-auto h-16 flex items-center justify-between px-4 sm:px-6 lg:mx-20 lg:px-0 xl:mx-8 text-white font-bold text-lg">
<span>${opts.title}</span>
<span data-testid="title">${opts.title}</span>
${opts.additionalAppBarContent ? wecco.html`<span>${opts.additionalAppBarContent}</span>` : ""}
</div>
</header>
Expand All @@ -24,7 +24,7 @@ export function appShell(opts: AppShellOptions): wecco.ElementUpdate {
<footer class="bg-blue-200 h-20 text-gray-600 text-xs flex items-center justify-around px-2">
<div>
Fate Core Remote Table v${opts.versionInfo.version} (${opts.versionInfo.commit}).
&copy; 2021 Alexander Metzner.
&copy; 2021-2024 Alexander Metzner.
<a href="https://github.com/halimath/fate-core-remote-table">github.com/halimath/fate-core-remote-table</a><br><br>
The Fate Core font is © Evil Hat Productions, LLC and is used with permission. The Four Actions icons were
designed by Jeremy Keller.
Expand All @@ -50,6 +50,7 @@ export interface ButtonOpts {
onClick?: ButtonCallback
size?: "s" | "m" | "l"
disabled?: boolean
testId?: string
}

export function button(opts: ButtonOpts): wecco.ElementUpdate {
Expand All @@ -58,7 +59,8 @@ export function button(opts: ButtonOpts): wecco.ElementUpdate {
color: opts.color ?? "blue",
onClick: opts.onClick ?? (() => void (0)),
size: opts.size ?? "m",
disabled: !!opts.disabled
disabled: !!opts.disabled,
testId: opts.testId,
}

const padding = (options.size === "s") ? 1 : ((options.size === "m") ? 2 : 4)
Expand All @@ -71,5 +73,5 @@ export function button(opts: ButtonOpts): wecco.ElementUpdate {
}

return wecco.html`<button @click=${options.onClick} ?disabled=${options.disabled}
class=${style}>${options.label}</button>`
class=${style} data-testid=${opts.testId}>${options.label}</button>`
}
2 changes: 1 addition & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
internal/boundary/public
internal/web/public
Loading

0 comments on commit 9b756aa

Please sign in to comment.