Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend Architecture Refactoring #12

Merged
merged 14 commits into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading