Compare commits

...

80 Commits

Author SHA1 Message Date
Settel
1243dec6b9 doc: fix typo 2024-02-19 08:11:04 +01:00
Settel
a7602eed6e version: bump to 3.4
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-02-19 08:10:09 +01:00
Settel
ae2f0407fa feat: use podman, if available or docker otherwise
doc: update README.md
2024-02-19 08:09:42 +01:00
Settel
c60976a06a version: bump to 3.3
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-18 19:24:54 +01:00
Settel
ad58cbc577 feat: add link to KnowYT website (instead of link to video) 2024-02-18 19:24:42 +01:00
Settel
40cdfe42be version: bump to 3.2 2024-02-18 14:10:17 +01:00
Settel
6f69ca91af chore: fix linter warnings 2024-02-18 14:10:17 +01:00
Settel
113a5fb16e chore: update npm libraries
bugfix: fix warning about ambiguous imports or types
2024-02-18 14:10:17 +01:00
Settel
a0c8e9592f version: bump to 3.1 2024-02-18 14:10:17 +01:00
Settel
512edbc62b feat: issue a warning on admin page when there're no teams yet 2024-02-18 14:10:17 +01:00
Settel
2bcb322488 bugfix: clear cameo cookie on logout, too 2024-02-18 14:10:17 +01:00
Settel
3a19c5b3f7 bugfix: fix typo 2024-02-18 14:10:17 +01:00
Settel
bde47843bf version: bump to 3.0 2024-02-18 14:10:17 +01:00
Settel
33bd7e8bab feat: use JWT for authentication
BREAKING CHANGE: changes format of cookie
2024-02-18 14:10:12 +01:00
Settel
bcc446ed16 feat: use JWT for authentication (WIP)
BREAKING CHANGE: changes format of cookie
2024-02-18 14:09:44 +01:00
Settel
0f0b2ede64 refactor: remove IsCameo flag from JSON 2024-02-18 10:34:13 +01:00
Settel
a06e50b704 refactor: extract checkAuthCode() 2024-02-18 10:29:20 +01:00
Settel
ca609fad94 refactor: move Cameo() to separate file 2024-02-18 10:26:31 +01:00
Settel
ca4dddec2e bugfix: fix linter warnings 2024-02-18 10:24:40 +01:00
Settel
a5257506da version: bump to 2.28
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-02 21:54:13 +01:00
Settel
8375a3b5bd chore: replaced deprecated package build-url with query-string 2024-02-02 21:49:03 +01:00
Settel
bad7e137ad chore: upgrade from ohmyfetch to ofetch after author renamed the package 2024-02-02 21:25:42 +01:00
Settel
a8ba49bda4 bugfix: fix warning about wrong import of type 2024-02-02 21:19:20 +01:00
Settel
163b1bbfc5 chore: update .woodpecker.yml to match new syntax requirements
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-01 12:10:47 +01:00
Settel
e25f803c8e chore: update .woodpecker.yml to match new syntax requirements
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
2024-02-01 12:09:05 +01:00
Settel
7ed8c73112 chore: update .woodpecker.yml to match new syntax requirements
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-01 12:07:21 +01:00
Settel
1d235c1583 chore: update .woodpecker.yml to match new syntax requirements
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-01 12:04:40 +01:00
Settel
96c04835bc chore: update README.md
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-01 11:53:36 +01:00
Settel
0b7db9297c version: bump to 2.27 2024-02-01 11:40:09 +01:00
Settel
3b86b04de2 feat: add link to explaination video 2024-02-01 11:39:49 +01:00
Settel
e6208e9ec2 chore: update Copyright notice to 2024 2024-02-01 11:39:35 +01:00
Settel
0e6fef049f doc: update README.md 2023-06-30 21:24:17 +02:00
Settel
b5191039ee version: bump to 2.26
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2023-06-19 13:13:22 +02:00
Settel
5d2d33ebe3 bugfix: fix typo 2023-06-19 13:13:11 +02:00
Settel
967cf68a7f version: bump to 2.25
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-06-19 10:42:17 +02:00
Settel
24c93623dc feat: show number of quotes played by player 2023-06-19 10:42:00 +02:00
Settel
fee9caff08 version: bump to 2.24
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-06-18 15:28:00 +02:00
Settel
c86dc32335 bugfix: read version from config.public.version instead of config.version 2023-06-18 15:27:46 +02:00
Settel
915c78dd58 version: bump to 2.23
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-06-18 15:08:28 +02:00
Settel
6787f1c3f8 feat: show checkmark for already played quotes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-06-18 15:07:45 +02:00
Settel
4cd1bf6984 version: bump to 2.22
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-06-18 11:39:48 +02:00
Settel
959ba20d98 bugfix: create missing directory 2023-06-18 11:39:48 +02:00
Settel
695d007fd7 chores: use pnpm 8.x.y, update pnpm-lock.yaml
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-06-18 11:36:49 +02:00
Settel
0d255c1ee5 bugfix: install pnpm in CI
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-06-18 11:31:25 +02:00
Settel
9ba74f5b2f version: bump to 2.21
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-06-18 11:21:17 +02:00
Settel
b359d30d8c feat: use pnpm instead of yarn 2023-06-18 11:20:50 +02:00
Settel
0f805962a4 chores: upgrade npm packages 2023-06-17 20:31:32 +02:00
Settel
7e4c344ec1 feat: publish to docker.io 2023-06-04 14:31:02 +02:00
Settel
7de4f1cad8 version: bump to 2.20
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
2023-06-04 11:40:46 +02:00
Settel
fcfed00d5b feat: check for slirp4netns tool during setup (needed for rootless Podman) 2023-06-04 11:40:31 +02:00
Settel
e4114837c4 version: bump to 2.19
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
2023-06-04 11:37:55 +02:00
Settel
66fa59ea2e feat: check for newuidmap tool during setup (needed for rootless Podman) 2023-06-04 11:37:41 +02:00
Settel
3577a9291a version: bump to 2.18
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
2023-06-04 11:30:43 +02:00
Settel
af89f6c9fb bugfix: create build/files/ folder during build process 2023-06-04 11:30:30 +02:00
Settel
555c26448a version: bump to 2.17
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
2023-06-04 11:26:25 +02:00
Settel
ef486010af chores: remove default admin account from repo 2023-06-04 11:26:00 +02:00
Settel
e726b8f990 version: bump to 2.16
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
2023-06-04 11:23:03 +02:00
Settel
9d4c6916a6 bugfix: bind to all interfaces by default (needed for Podman/Docker) 2023-06-04 11:22:49 +02:00
Settel
0cd78c5af9 version: bump to 2.15 2023-06-03 23:05:33 +02:00
Settel
a78ed70f05 feat: setup game and create admin user 2023-06-03 23:05:06 +02:00
Settel
846f137b7a feat: setup app, create admin user (WIP) 2023-06-03 22:39:36 +02:00
Settel
c871418d07 Merge branch 'develop' of git.gnuher.de:settel/knowyt into develop 2023-06-03 21:20:41 +02:00
Settel
78b4947730 feat: show create admin account dialog (WIP) 2023-06-03 21:09:16 +02:00
Settel
dd653c39f4 i18n: add i18n translation 2023-05-17 22:56:08 +03:00
Settel
3f8df8b538 bugfix: create data dir if not present 2023-05-17 22:55:44 +03:00
Settel
415c42cefd feat: check for more dependencies during preflight check 2023-05-17 22:55:30 +03:00
Settel
0367fa6987 bugfix: fix build chain matrix notification plugin
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-03 20:23:47 +01:00
Settel
1876484590 Merge branch 'prod' into develop 2023-02-02 15:01:16 +01:00
Settel
21a24632b2 Merge branch 'develop' of git.gnuher.de:settel/knowyt into develop 2023-02-02 14:59:32 +01:00
Settel
059035e4fd create client's output dir before setting up server 2023-02-02 14:58:59 +01:00
Settel
6b9394a50a create admin account and show PIN (WIP)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-29 18:19:34 +01:00
Settel
0d2ae30b24 refactor: move version/copyright notice to own component
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-29 17:50:20 +01:00
Settel
7cd0639708 fix bug in IsInitialized() 2023-01-29 17:49:47 +01:00
Settel
e5bfd05e71 add text to /setup page
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-29 17:19:51 +01:00
Settel
cfd16e8e6c refactor CheckSetup() 2023-01-29 16:14:23 +01:00
Settel
4967d6bdf1 add new page /setup 2023-01-29 16:07:24 +01:00
Settel
37198001a3 add new role "setup" if game is not initialized yet 2023-01-29 16:06:13 +01:00
Settel
ea7caa322b remove ServerinfoStore for it's not needed 2023-01-29 15:36:57 +01:00
Settel
1dcd26ffcf add comments 2023-01-29 15:36:06 +01:00
Settel
a243deb79e init game when database is empty
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
bind server to interface given by CLI option
2023-01-29 15:06:46 +01:00
146 changed files with 7466 additions and 4920 deletions

View File

@ -1,18 +1,23 @@
pipeline:
when:
branch: prod
steps:
frontend:
image: node:16-alpine
image: node:18-alpine
commands:
- ( cd client && yarn install )
- ( cd client/ && yarn generate )
- yarn global add pnpm
- ( cd client && pnpm run setup )
- ( cd client/ && pnpm run generate )
- mkdir -p client/.output/public/
backend:
image: golang:1.18-alpine
image: golang:1.20-alpine
commands:
- apk add go-bindata
- apk add make
- make -C server setup
- make -C server build
- mkdir -p build/files/data
- cp server/knowyt build/files/
build-publish:
@ -28,7 +33,7 @@ pipeline:
tags: latest
notify:
image: plugins/matrix
image: thegeeklab/drone-matrix
settings:
homeserver: https://matrix.bw-messenger.de/
roomid: wHCOKvEHLUmsNybNwh:matrix.bw-messenger.de
@ -36,5 +41,3 @@ pipeline:
from_secret: matrix.bw-messenger.de.username
password:
from_secret: matrix.bw-messenger.de.password
branches: prod

View File

@ -1,7 +1,8 @@
TMUX_SESSION=knowyt
VERSION=$(shell grep version client/package.json | cut -d\" -f4)
CONTAINER=$(shell which podman || which docker)
.PHONY: info run-all run-server run-client run-tmux build podman clean
.PHONY: info setup run-all run-server run-client run-tmux build clean
info:
@echo available targets:
@ -9,7 +10,16 @@ info:
setup:
@echo "I checking for tools"
@for binary in go tmux node yarn npx podman; do \
@echo -n " container: " ; \
if ! [ -x "$(CONTAINER)" ]; then \
echo "neither podman nor docker found" ;\
exit 1 ;\
else \
echo "$(CONTAINER)" ;\
fi
@for binary in go go-bindata tmux node pnpm npx \
pexec inotifyloop inotifywait \
newuidmap slirp4netns; do \
echo -n " $$binary: " ; \
if ! which "$$binary"; then \
echo "not found" ;\
@ -17,11 +27,11 @@ setup:
fi ;\
done
@echo "I installing client dependencies"
( cd client && yarn install )
@echo "I installing server dependencies"
$(MAKE) -C server setup
( cd client && pnpm run setup )
@echo "I create client output directory"
mkdir -p client/.output/public/
@echo "I installing server dependencies"
$(MAKE) -C server setup
run-all:
pexec -R -c -e TARGET \
@ -39,32 +49,36 @@ run-tmux:
tmux attach-session -t "$(TMUX_SESSION)"
run-client:
(cd client/ && yarn dev)
(cd client/ && pnpm dev)
run-server:
$(MAKE) -C server run-loop
build:
echo $(VERSION)
(cd client/ && yarn generate)
(cd client/ && pnpm run generate)
$(MAKE) -C server build
$(MAKE) podman-build
$(MAKE) podman-save
$(MAKE) container-build
podman-build:
container-build:
mkdir -p build/files/data
cp server/knowyt build/files/
podman build --tag knowyt:$(VERSION) .
$(CONTAINER) build --tag knowyt:$(VERSION) .
podman-save:
container-save:
rm -f build/knowyt-$(VERSION).tar
podman save knowyt:$(VERSION) -o build/knowyt-$(VERSION).tar
$(CONTAINER) save knowyt:$(VERSION) -o build/knowyt-$(VERSION).tar
ls -lh build/knowyt-$(VERSION).tar
podman-run:
podman run --rm -it -p 32039:32039 -v $$(pwd)/server/data/:/data --name knowyt knowyt:$(VERSION)
container-run:
$(CONTAINER) run --rm -it -p 8080:32039 -v $$(pwd)/server/data/:/data --name knowyt knowyt:$(VERSION)
podman-stop:
podman stop knowyt
container-stop:
$(CONTAINER) stop knowyt
container-publish:
$(CONTAINER) push knowyt:$(VERSION) docker.io/settel/knowyt:$(VERSION)
$(CONTAINER) push knowyt:$(VERSION) docker.io/settel/knowyt:latest
clean:
rm -rf client/.output/

View File

@ -1,22 +1,45 @@
# Know Your Teammates
Know Your Teammates is a free team building game for 3-20 players that can be played in a browser.
It has two phases: during collection phase, the players are asked to enter 3-5 facts about themselves, eg. hobbies, books, movies they enjoy, places they have visited or other fun facts. In play phase, one fact is presented to all players and they have to guess who wrote it. The real fun is in the discussion following it :-)
[Watch video (3:30mins)](https://www.sirlab.de/knowyt/know-your-teammates.mp4)
See website https://www.sirlab.de/linux/games/knowyt/
## Installation
install dependencies and check for missing tools
```
make setup
```
## run (development mode)
start client and server in a split terminal window
```
make run-tmux
```
This will start frontend (port 3000) and backend (port 32039) as two separate services. Hot reload is active, changes will take effect immediately.
## build podman/docker image
Point your browser at [http://localhost:3000/](http://localhost:3000/) to start playing.
## build and run podman/docker image
```
make setup # only needed on first run
make build
make container-run
```
@ -27,4 +50,6 @@ AGPLv3 (GNU Affero General Public License)
## Author
© 2021-2022 Achim Settelmeier <knowyt@m1.sirlab.de>
© 2021-2024 Achim Settelmeier <knowyt@m1.sirlab.de>
https://www.sirlab.de/

View File

@ -1,5 +0,0 @@
{
"authcode": "646162",
"name": "Settel (Admin)",
"role": "admin"
}

View File

@ -1,25 +1,35 @@
{
"name": "knowyt",
"version": "2.14",
"version": "3.4",
"private": true,
"scripts": {
"lint": "tsc-strict",
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview"
"setup": "pnpm install"
},
"devDependencies": {
"nuxt": "^3.0.0",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript-strict-plugin": "^2.1.0"
"@types/node": "^18.19.17",
"http-proxy": "^1.18.1",
"nuxt": "^3.10.2",
"sass": "^1.71.0",
"sass-loader": "^13.3.3",
"typescript-strict-plugin": "^2.3.0",
"webpack": "^5.90.2"
},
"dependencies": {
"@pinia/nuxt": "^0.4.4",
"build-url": "^6.0.1",
"nuxt-icons": "^3.0.0",
"typescript": "^4.9.3",
"vue-contenteditable": "^4.1.0"
"@pinia/nuxt": "^0.4.11",
"@vue/reactivity": "^3.4.19",
"@vue/runtime-core": "^3.4.19",
"@vue/runtime-dom": "^3.4.19",
"@vue/shared": "^3.4.19",
"nuxt-icons": "^3.2.1",
"ofetch": "^1.3.3",
"query-string": "^8.2.0",
"typescript": "^5.3.3",
"vue": "^3.4.19",
"vue-contenteditable": "^4.1.0",
"vue-router": "^4.2.5"
}
}

6715
client/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="128"
height="100"
viewBox="0 0 128 100"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="checkmark.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="2.3786088"
inkscape:cx="10.720552"
inkscape:cy="21.441105"
inkscape:window-width="1920"
inkscape:window-height="1181"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1"
showborder="true" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:#4bc417;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 93.253283,6.1273107 C 100.05266,14.282038 108.74264,17.439899 117.91216,22.197816 104.79242,27.143529 52.350943,76.132207 44.577742,96.156471 42.587301,88.868507 16.14146,63.021362 10.575889,61.270758 c 6.682863,-3.009693 20.41554,-9.619427 23.978573,-15.275893 0.758717,5.579065 9.653081,21.558694 9.653081,21.558694 8.596139,-4.086698 48.882074,-51.182042 49.04574,-61.4262483 z"
id="path790"
sodipodi:nodetypes="ccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -27,6 +27,7 @@ const createNewQuote = () => {
newQuote.value = {
id: ':new:' + Date.now(),
quote: '',
isPlayed: false,
}
}

View File

@ -0,0 +1,33 @@
<template>
<div class="copyright-notice__version" @click="openInfoModal">
v{{ config.public.version }}, © 2021-2024, Settel
</div>
<InfoModal v-if="showInfoModal" @close="closeInfoModal" />
</template>
<script setup lang="ts">
import { useRuntimeConfig } from '#app'
import { ref, computed } from 'vue'
const config = useRuntimeConfig()
const showInfoModal = ref(false)
const openInfoModal = () => { showInfoModal.value = true }
const closeInfoModal = () => { showInfoModal.value = false }
</script>
<style lang="scss">
.copyright-notice {
&__version {
position: absolute;
right: 1em;
bottom: 0;
color: #606060;
cursor: pointer;
&:hover {
color: #c0c0c0;
}
}
}
</style>

View File

@ -37,7 +37,12 @@ const emit = defineEmits(['close'])
border-radius: 8px;
color: $dialog-box-text-color;
z-index: 12;
@media (max-width: $phone-max-width) {
min-width: unset;
}
}
&__header {
display: flex;

View File

@ -11,9 +11,10 @@
<script setup lang="ts">
import { useRouter } from '#app'
import { ref, Ref } from 'vue'
import { ref } from 'vue'
import useAuth from '@/composables/useAuth';
import useI18n from '@/composables/useI18n';
import type { Ref } from 'vue'
const { $t } = useI18n({
'login': { en: 'log in', de: 'login'},

View File

@ -13,7 +13,7 @@
</template>
<script setup lang="ts">
import { Player } from '@/composables/engine.d';
import type { Player } from '@/composables/engine.d';
defineProps<{
player: Player,

View File

@ -1,7 +1,10 @@
<template>
<div :class="cssClasses">
<div v-if="quote.isPlayed" class="quote-card__marker__is-played">
<nuxt-icon name="checkmark" filled />
</div>
<div v-if="editable && !isEditMode" class="quote-card__action-buttons">
<QuoteCardActionButton @click="editQuote" icon="edit" />
<QuoteCardActionButton v-if="!quote.isPlayed" @click="editQuote" icon="edit" />
<QuoteCardActionButton @click="deleteQuote" icon="trash" />
</div>
<div class="quote-card__text-container">
@ -180,5 +183,18 @@ const keydown = async (ev: KeyboardEvent) => {
top: -24px;
display: flex;
}
&__marker__is-played {
position: absolute;
left: -28px;
top: -40px;
width: 128px;
height: 100px;
.nuxt-icon svg {
width: unset;
height: unset;
}
}
}
</style>

View File

@ -1,32 +1,37 @@
<template>
<AdminInfoTile title="Games">
<table class="gameinfos-tile__table">
<tr class="gameinfos-tile__row">
<th class="gameinfos-tile__table-head">{{ $t('team-name')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('lang')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('state')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('num-players')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('gamemasters')}}</th>
</tr>
<tr
:class="{ 'gameinfos-tile__row': true, 'gameinfos-tile__row__disabled': game.state == 'disabled' }"
v-for="game in games"
:key="game.id"
@click="selectGame(game)"
>
<div>
<AdminInfoTile v-if="gamesCount > 0" title="Games">
<table class="gameinfos-tile__table">
<tr class="gameinfos-tile__row">
<th class="gameinfos-tile__table-head">{{ $t('team-name')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('lang')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('state')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('num-players')}}</th>
<th class="gameinfos-tile__table-head">{{ $t('gamemasters')}}</th>
</tr>
<tr
:class="{ 'gameinfos-tile__row': true, 'gameinfos-tile__row__disabled': game.state == 'disabled' }"
v-for="game in games"
:key="game.id"
@click="selectGame(game)"
>
<td class="gameinfos-tile__cell">{{ game.name }}</td>
<td class="gameinfos-tile__cell">{{ game.lang }}</td>
<td class="gameinfos-tile__cell">{{ game.state }}</td>
<td class="gameinfos-tile__cell">{{ game.players.length }}</td>
<td class="gameinfos-tile__cell">{{ getGamemastersFromGame(game).join(', ') }}</td>
</tr>
</table>
</AdminInfoTile>
</tr>
</table>
</AdminInfoTile>
<AdminInfoTile v-if="gamesCount == 0" :title="$t('no-games-1')">
<p>{{ $t('no-games-2') }}</p>
</AdminInfoTile>
</div>
</template>
<script setup lang="ts">
import { navigateTo } from "#app"
import { ref } from 'vue'
import { ref, computed } from 'vue'
import useI18n from '@/composables/useI18n'
import useEngine from '@/composables/useEngine'
import type { GameInfo } from '@/composables/engine.d'
@ -37,11 +42,16 @@ const { $t } = useI18n({
state: { en: 'State', de: 'Status' },
'num-players': { en: '# Players', de: '# Spieler' },
gamemasters: { en: 'Gamemaster(s)', de: 'Gamemaster(s)' },
'no-games-1': { en: 'The list of teams is empty', de: 'Die Liste der Teams ist noch leer'},
'no-games-2': { en: 'Log out and click the "create team" button on the start page.', de: 'Logge dich aus und klicke auf der Startseite auf "Team erstellen".'},
})
const { fetchGameInfos, cameo } = useEngine()
const games = ref(await fetchGameInfos())
const gamesCount = computed(() => {
return Object.keys(games.value).length
})
const getGamemastersFromGame = (game: GameInfo) => game.players.filter((player) => player.role === 'gamemaster').map((player) => player.name)
const selectGame = async (game: GameInfo): Promise<void> => {

View File

@ -30,7 +30,7 @@ const emit = defineEmits(['icon-top-click', 'icon-bottom-click'])
&__container {
position: relative;
min-width: 300px;
margin: 40px;
margin: 16px;
padding: 16px 30px;
background-color: $admin-tile-background-color;
border: $admin-tile-border;

View File

@ -22,8 +22,8 @@
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue'
import { PlayerEdit } from '@/composables/engine.d'
import { onMounted, ref } from 'vue'
import type { PlayerEdit } from '@/composables/engine.d'
import type { Button } from '@/components/admin/PlayerModal'
const props = defineProps<{

View File

@ -3,10 +3,10 @@
<AdminInfoTile title="Players" icon-top="reload" @icon-top-click="reload" icon-bottom="add" @icon-bottom-click="addPlayer">
<table class="players-tile__table">
<tr>
<th class="players-tile__table-head">{{ $t('name') }}:</th>
<th class="players-tile__table-head">{{ $t('num-quotes') }}</th>
<th class="players-tile__table-head">{{ $t('score') }}</th>
<th class="players-tile__table-head">{{ $t('last-logged-in') }}</th>
<th class="players-tile__table-head players-tile__cell">{{ $t('name') }}:</th>
<th class="players-tile__table-head players-tile__cell">{{ $t('num-quotes') }}</th>
<th class="players-tile__table-head players-tile__cell">{{ $t('score') }}</th>
<th class="players-tile__table-head players-tile__cell">{{ $t('last-logged-in') }}</th>
</tr>
<tr v-for="player in players" class="players-tile__row" :key="player.id" @click="editPlayer(player)">
<td class="players-tile__cell">
@ -15,7 +15,7 @@
<nuxt-icon name="crown" filled />
</div>
</td>
<td class="players-tile__cell">{{ player.numQuotes }}</td>
<td class="players-tile__cell">{{ player.numQuotesPlayed }} / {{ player.numQuotes }}</td>
<td class="players-tile__cell">{{ player.score }}</td>
<td class="players-tile__cell">{{ !player.isIdle ? 'online' : player.lastLoggedIn === 0 ? '-' : datetime(player.lastLoggedIn) }}</td>
</tr>
@ -42,7 +42,7 @@ const emit = defineEmits(['update'])
const { $t } = useI18n({
name: { en: 'Name', de: 'Name' },
'num-quotes': { en: '# quotes', de: '# Quotes' },
'num-quotes': { en: '# quotes played', de: '# Aussagen gespielt' },
'score': { en: 'Score', de: 'Score' },
'last-logged-in': { en: 'last logged in', de: 'zuletzt eingeloggt' },
})
@ -135,7 +135,7 @@ const playerDialogSubmit = async (action: ButtonAction): Promise<void> => {
}
&__cell {
padding-right: 8px;
padding-right: 16px;
}
&__is-gamemaster {

View File

@ -18,12 +18,14 @@ export type PlayerInfo = PlayerEdit & {
lastLoggedIn: number
isPlaying: boolean
numQuotes: number
numQuotesPlayed: number
role: Role
}
export type Quote = {
id: string
quote: string
isPlayed: boolean
}
export type Quotes = Array<Quote>

View File

@ -1,14 +1,13 @@
import buildUrl from 'build-url'
import queryString from 'query-string'
export type QueryParams = {
[name: string]: string
}
export async function callApi(path: string, queryParams?: QueryParams) {
const url = buildUrl('/', {
path,
queryParams,
})
const url = path + (
queryParams ? '?' + queryString.stringify(queryParams) : ''
)
return await $fetch(url)
}

View File

@ -3,9 +3,9 @@ import { useUserinfoStore } from "@/stores/UserinfoStore"
import { useGameinfoStore } from "@/stores/GameinfoStore"
import { useRoundStore } from "@/stores/RoundStore"
import { usePlayersStore } from "@/stores/PlayersStore"
import { EngineContext } from '@/composables/useEngine'
import useAlert from '@/composables/useAlert'
import useI18n from '@/composables/useI18n'
import type { EngineContext } from '@/composables/useEngine'
type EngineResponse = {
version: string,

View File

@ -1,5 +1,5 @@
import { EngineContext } from '@/composables/useEngine'
import { useUserinfoStore } from "@/stores/UserinfoStore"
import type { EngineContext } from '@/composables/useEngine'
import type { GameInfo, GameInfos, PlayerEdit, Lang, CreateGameStatus } from '@/composables/engine.d'
export async function fetchGameInfo(this: EngineContext): Promise<GameInfo> {

View File

@ -1,5 +1,5 @@
import { EngineContext } from '@/composables/useEngine'
import { useUserinfoStore } from "@/stores/UserinfoStore"
import type { EngineContext } from '@/composables/useEngine'
export async function collectQuotes(this: EngineContext): Promise<void> {
const userInfoStore = useUserinfoStore()

View File

@ -1,5 +1,5 @@
import { useUserinfoStore } from "@/stores/UserinfoStore"
import { EngineContext } from '@/composables/useEngine'
import type { EngineContext } from '@/composables/useEngine'
export async function saveSelection(this: EngineContext, selection: string): Promise<void> {
const userInfoStore = useUserinfoStore()

View File

@ -1,7 +1,8 @@
import { Ref, ref } from 'vue'
import { ref } from 'vue'
import type { Quotes } from '@/composables/engine.d'
import { useUserinfoStore } from "@/stores/UserinfoStore"
import { EngineContext } from '@/composables/useEngine'
import type { Ref } from 'vue'
import type { EngineContext } from '@/composables/useEngine'
type QuotesResponse = {
quotes: Quotes

View File

@ -0,0 +1,7 @@
import type { EngineContext } from '@/composables/useEngine'
export async function setupApp(this: EngineContext, authcode: string): Promise<void> {
await this.callApi('/api/setupApp', {
authcode,
})
}

View File

@ -1,6 +1,6 @@
import { useUserinfoStore } from "@/stores/UserinfoStore"
import { EngineContext } from '@/composables/useEngine'
import useAlert from '@/composables/useAlert'
import type { EngineContext } from '@/composables/useEngine'
export function start(this: EngineContext): void {
if (this.isActive && !this.shouldStop) {

View File

@ -1,4 +1,5 @@
import { Ref, ref } from 'vue'
import { ref } from 'vue'
import type { Ref } from 'vue'
export type AlertMessages = Array<string>

View File

@ -1,12 +1,13 @@
import { useUserinfoStore, Userinfo } from '@/stores/UserinfoStore'
import { useUserinfoStore } from '@/stores/UserinfoStore'
import { useEngineStore } from '@/stores/EngineStore'
import { useGameinfoStore } from '@/stores/GameinfoStore'
import { usePlayersStore } from '@/stores/PlayersStore'
import { useRoundStore } from '@/stores/RoundStore'
import useI18n from './useI18n'
import { $fetch } from 'ohmyfetch'
import { $fetch } from 'ofetch'
export type AllowRole = '' | 'player' | 'gamemaster' | 'admin'
import type { Userinfo } from '@/stores/UserinfoStore'
export type AllowRole = '' | 'player' | 'gamemaster' | 'admin' | 'setup'
export type AllowRoles = Array<AllowRole>
export interface useAuth {
@ -25,8 +26,17 @@ export default (): useAuth => {
user.setUserInfo(userInfo)
useI18n({}).setLang(userInfo.lang)
if (allowRoles.indexOf(userInfo.role) >= 0 ) {
// user is authenticated and authorized, let the user in
return
}
// game is not initialized yet, needs setup
if (userInfo.role === 'setup') {
document.location.pathname = '/setup'
return
}
// user is authenticated but not authorized for this page
if (user.isAdmin) {
document.location.pathname = '/admin'
// can't use navigateTo() for it fails with DOMException if two consecutive redirects happen (at least in docker container)
@ -35,6 +45,7 @@ export default (): useAuth => {
document.location.pathname = '/play'
}
} catch (e) {
// user is not authenticated
if (allowRoles.indexOf('') == -1 ) {
document.location.pathname = '/'
}

View File

@ -1,11 +1,14 @@
import { Ref, ref } from 'vue'
import { callApi, QueryParams } from '@/composables/engine/callApi'
import { ref } from 'vue'
import { callApi } from '@/composables/engine/callApi'
import { start, stop } from '@/composables/engine/startStop'
import { setupApp } from '@/composables/engine/setupApp'
import { fetchUpdate } from '@/composables/engine/fetchUpdate'
import { loadQuotes, getQuotesRef, deleteQuote, saveQuote } from '@/composables/engine/quotes'
import { fetchGameInfo, fetchGameInfos, setGameLang, setGameName, savePlayer, removePlayer, createGame, cameo, logoutCameo } from '@/composables/engine/gameManagement'
import { collectQuotes, startGame, continueGame, resetGame, finishGame, disableGame, removeGame } from '@/composables/engine/gameState'
import { saveSelection } from '@/composables/engine/play'
import type { Ref } from 'vue'
import type { QueryParams } from '@/composables/engine/callApi'
import type { Quotes, GameInfo, GameInfos, PlayerEdit, Lang, CreateGameStatus } from '@/composables/engine.d'
export interface EngineContext {
@ -48,6 +51,7 @@ export interface useEngine {
createGame: (name: string, teamname: string, lang: Lang) => Promise<CreateGameStatus>
cameo: (authcode: string) => Promise<void>
logoutCameo: () => Promise<void>
setupApp: (authcode: string) => Promise<void>
}
export default (): useEngine => {
@ -92,5 +96,6 @@ export default (): useEngine => {
createGame: (name: string, teamname: string, lang: Lang) => createGame.apply(context, [name, teamname, lang]),
cameo: (authcode: string) => cameo.apply(context,[authcode]),
logoutCameo: () => logoutCameo.apply(context),
setupApp: (authcode) => setupApp.apply(context, [authcode]),
}
}

View File

@ -24,6 +24,7 @@ await useAuth().authenticateAndLoadUserInfo(['admin'])
&__tiles {
display: flex;
margin: 24px;
}
}
</style>

View File

@ -42,6 +42,7 @@ updateGameinfo()
&__tiles {
display: flex;
margin: 24px;
}
&__tiles-spacer {

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="page-index__page">
<TitleBox />
<div class="page-index__action-box">
<div class="page-index__space" />
@ -12,34 +12,38 @@
</div>
<div class="page-index__space" />
</div>
<div class="page-index__copyright-notice" @click="openInfoModal">
v{{ config.version }}, © 2021-2022, Settel
<div class="page-index__about">
<a
href="https://www.sirlab.de/linux/games/knowyt/"
target="_blank" rel="noopener"
>
<Button :border="false">
<div class="page-index__about-text">{{ $t('about') }}</div>
</Button>
</a>
</div>
<InfoModal v-if="showInfoModal" @close="closeInfoModal" />
<CopyrightNotice />
<CreateTeamDialog v-if="showCreateTeamDialog" @close="closeCreateTeamDialog" />
</div>
</template>
<script setup lang="ts">
import { useRuntimeConfig, navigateTo } from '#app'
import { ref } from 'vue'
import useAuth from '@/composables/useAuth'
import useI18n from '@/composables/useI18n'
import CopyrightNotice from '../components/CopyrightNotice.vue';
const { $t } = useI18n({
'create-team': { en: 'Create Team ...', de: 'Team erstellen ...' },
'about': { en: 'about the game', de: 'Über das Spiel' },
})
const config = useRuntimeConfig()
await useAuth().authenticateAndLoadUserInfo([''])
const showCreateTeamDialog = ref(false)
const showInfoModal = ref(false)
const createTeam = () => { showCreateTeamDialog.value = true }
const closeCreateTeamDialog = () => { showCreateTeamDialog.value = false }
const openInfoModal = () => { showInfoModal.value = true }
const closeInfoModal = () => { showInfoModal.value = false }
</script>
<style lang="scss">
@ -53,6 +57,11 @@ body {
}
.page-index {
&__page {
display: flex;
flex-direction: column;
}
&__action-box {
display: flex;
width: 100%;
@ -93,16 +102,9 @@ body {
flex-grow: 1;
}
&__copyright-notice {
position: absolute;
right: 1em;
bottom: 0;
color: #606060;
cursor: pointer;
&:hover {
color: #c0c0c0;
}
&__about {
margin: 48px 0;
align-self: center;
}
}
</style>

111
client/src/pages/setup.vue Normal file
View File

@ -0,0 +1,111 @@
<template>
<div>
<TitleBox />
<div class="page-setup__action-box">
<div class="page-setup__description">
<p>{{ $t('description-1') }}</p>
<p>{{ $t('description-2') }}</p>
</div>
<div class="page-setup__button">
<Button @click="openModal">{{ $t('create admin user') }}</Button>
<ModalDialog v-if="showAdminAccountCreatedDialog" :no-close-button="true" @close="showAdminAccountCreatedDialog = false">
<div class="page-setup__auth-message">{{ $t('pin') }}</div>
<div class="page-setup__pin">{{ authcode }}</div>
<div class="page-setup__auth-message">{{ $t('remember-and-log-in') }}</div>
<template v-slot:footer>
<div class="page-setup__cta">
<Button @click="createAdminAccount" :disabled="saveInProgress">{{ $t('complete-setup') }}</Button>
</div>
</template>
</ModalDialog>
</div>
</div>
<CopyrightNotice />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import useAuth from '@/composables/useAuth'
import useI18n from '@/composables/useI18n'
import useEngine from '@/composables/useEngine'
const { $t } = useI18n({
'create admin user': { en: 'create admin user', de: 'Admin-Benutzer anlegen' },
'description-1': { en: 'Congratulation!', de: 'Herzlichen Glückwunsch!' },
'description-2': { en: 'You\'ve successfully installed Know Your Teammates.', de: 'Know Your Teammates wurde erfolgreich installiert.' },
'pin': { en: 'Your pin code is:', de: 'Deine PIN lautet:' },
'remember-and-log-in': { en: 'Write it down now.', de: 'Schreibe sie Dir jetzt auf.' },
'complete-setup': { en: 'Complete setup', de: 'Setup abschließen' },
})
await useAuth().authenticateAndLoadUserInfo(['setup'])
const showAdminAccountCreatedDialog = ref(false)
const saveInProgress = ref(false)
const authcode = ref('000000')
const openModal = () => {
authcode.value = ''
for (var i = 0; i < 6; i++) {
authcode.value += '' + Math.floor(Math.floor(Math.random() * 10000) / 100) % 10
}
showAdminAccountCreatedDialog.value = true
saveInProgress.value = false
}
const createAdminAccount = async () => {
saveInProgress.value = true
await useEngine().setupApp(authcode.value)
saveInProgress.value = false
showAdminAccountCreatedDialog.value = false
document.location.pathname = "/"
}
</script>
<style lang="scss">
@import '~/assets/css/components';
.page-setup {
&__action-box {
display: flex;
flex-direction: column;
width: 340px;
margin: 0 auto;
@media (max-width: $phone-max-width) {
margin: 32px 0 0 0;
width: 100%;
}
}
&__description {
font-size: 24px;
color: #ffffff;
text-align: center;
}
&__auth-message {
font-family: $font-secondary;
font-size: 24px;
text-align: center;
margin: 32px;
}
&__pin {
font-family: $font-secondary;
font-size: 32px;
font-weight: 800;
text-align: center;
}
&__button {
display: flex;
margin: 32px auto;
}
&__cta {
margin: 0 auto;
}
}
</style>

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { Players } from '@/composables/engine.d';
import type { Players } from '@/composables/engine.d';
export const usePlayersStore = defineStore('PlayersStore', {
state: () => {

View File

@ -3,7 +3,7 @@ import { defineStore } from 'pinia'
export type Userinfo = {
id: string
name: string
role: '' | 'player' | 'gamemaster' | 'admin'
role: '' | 'player' | 'gamemaster' | 'admin' | 'setup'
game: string
lang: 'de' | 'en'
isCameo: string

File diff suppressed because it is too large Load Diff

2
server/.gitignore vendored
View File

@ -1,2 +1,2 @@
knowyt
data/games/*/state.json
data/

View File

@ -7,6 +7,7 @@ setup:
build:
-mkdir -p ../client/.output/public/
-mkdir data/
$(MAKE) generate
cd src/ && CGO_ENABLED=0 go build -o ../knowyt knowyt.go

View File

@ -1 +0,0 @@
{"name":"Massive Demo","lang":"de","created":1651603106}

View File

@ -1,4 +0,0 @@
{
"quote": "12.2",
"source": "9c5a22d3-1e82-4bad-95a4-c9efb169ede0"
}

View File

@ -1,4 +0,0 @@
{
"quote": "1.1",
"source": "4b1c22b8-6fa1-4c9d-98d7-cbf498035074"
}

View File

@ -1,4 +0,0 @@
{
"quote": "11.2",
"source": "cede08c4-768a-4792-b92f-8df162a07307"
}

View File

@ -1,4 +0,0 @@
{
"quote": "4.1",
"source": "b190ebb1-86c0-4308-a6b6-bf34237c10f8"
}

View File

@ -1,4 +0,0 @@
{
"quote": "Admin #1",
"source": "f30802dc-1c18-4169-99fe-04d1d8e7bd9e"
}

View File

@ -1,4 +0,0 @@
{
"quote": "7.2",
"source": "3945635e-c65a-4fb7-a46c-675ec53abebe"
}

View File

@ -1,4 +0,0 @@
{
"quote": "16.1",
"source": "49295e5b-0d0a-44ea-9bff-e74acaaa6dcf"
}

View File

@ -1,4 +0,0 @@
{
"quote": "3.2",
"source": "4fa78612-accd-491d-93e6-cca251ac0e5a"
}

View File

@ -1,4 +0,0 @@
{
"quote": "5.1",
"source": "3c60f533-676b-4464-8542-1f3e6fc49d13"
}

View File

@ -1,4 +0,0 @@
{
"quote": "Admin #2",
"source": "f30802dc-1c18-4169-99fe-04d1d8e7bd9e"
}

View File

@ -1,4 +0,0 @@
{
"quote": "2.2",
"source": "da6fdb50-8773-40ce-889f-c6f565ca35e3"
}

View File

@ -1,4 +0,0 @@
{
"quote": "6.1",
"source": "38f508e0-b808-4d28-be5b-d2f5cb54fd69"
}

View File

@ -1,4 +0,0 @@
{
"quote": "4.2",
"source": "b190ebb1-86c0-4308-a6b6-bf34237c10f8"
}

View File

@ -1,4 +0,0 @@
{
"quote": "13.1",
"source": "233dd18b-cb0d-4f77-b0ee-248dc7fec65d"
}

View File

@ -1,4 +0,0 @@
{
"quote": "9.2",
"source": "f99ee1de-af5c-4d7e-a1f4-622ad0cd40ac"
}

View File

@ -1,4 +0,0 @@
{
"quote": "12.1",
"source": "9c5a22d3-1e82-4bad-95a4-c9efb169ede0"
}

View File

@ -1,4 +0,0 @@
{
"quote": "5.2",
"source": "3c60f533-676b-4464-8542-1f3e6fc49d13"
}

View File

@ -1,4 +0,0 @@
{
"quote": "9.1",
"source": "f99ee1de-af5c-4d7e-a1f4-622ad0cd40ac"
}

View File

@ -1,4 +0,0 @@
{
"quote": "8.2",
"source": "81cf1907-8566-4b03-a433-3f6ea9bf8c85"
}

View File

@ -1,4 +0,0 @@
{
"quote": "15.1",
"source": "4706f51a-c014-4e0a-99f7-866e98b19986"
}

View File

@ -1,4 +0,0 @@
{
"quote": "1.2",
"source": "4b1c22b8-6fa1-4c9d-98d7-cbf498035074"
}

View File

@ -1,4 +0,0 @@
{
"quote": "11.1",
"source": "cede08c4-768a-4792-b92f-8df162a07307"
}

View File

@ -1,4 +0,0 @@
{
"quote": "10.2",
"source": "8f688b3b-6e2f-4bf2-bdfa-03762f2c7b72"
}

View File

@ -1,4 +0,0 @@
{
"quote": "6.2",
"source": "38f508e0-b808-4d28-be5b-d2f5cb54fd69"
}

View File

@ -1,4 +0,0 @@
{
"quote": "13.2",
"source": "233dd18b-cb0d-4f77-b0ee-248dc7fec65d"
}

View File

@ -1,4 +0,0 @@
{
"quote": "8.1",
"source": "81cf1907-8566-4b03-a433-3f6ea9bf8c85"
}

View File

@ -1,4 +0,0 @@
{
"quote": "10.1",
"source": "8f688b3b-6e2f-4bf2-bdfa-03762f2c7b72"
}

View File

@ -1,4 +0,0 @@
{
"quote": "16.2",
"source": "49295e5b-0d0a-44ea-9bff-e74acaaa6dcf"
}

View File

@ -1,4 +0,0 @@
{
"quote": "14.2",
"source": "51c4c7cb-8382-4bef-ad2d-c457c5af12f2"
}

View File

@ -1,4 +0,0 @@
{
"quote": "2.1",
"source": "da6fdb50-8773-40ce-889f-c6f565ca35e3"
}

View File

@ -1,4 +0,0 @@
{
"quote": "14.1",
"source": "51c4c7cb-8382-4bef-ad2d-c457c5af12f2"
}

View File

@ -1,4 +0,0 @@
{
"quote": "3.1",
"source": "4fa78612-accd-491d-93e6-cca251ac0e5a"
}

View File

@ -1,4 +0,0 @@
{
"quote": "7.1",
"source": "3945635e-c65a-4fb7-a46c-675ec53abebe"
}

View File

@ -1,4 +0,0 @@
{
"quote": "15.2",
"source": "4706f51a-c014-4e0a-99f7-866e98b19986"
}

View File

@ -1 +0,0 @@
{"name":"Bumsquatsch","lang":"de","created":1670881903}

View File

@ -1,5 +0,0 @@
{
"quote": "Extra Bumsquatsch vom Oberadmin",
"source": "6ef8620b-2b5e-4749-821e-b0722b8d8117",
"created": 1670882004
}

View File

@ -1,5 +0,0 @@
{
"quote": "Bumsquatsch #2",
"source": "c5e0cbf4-1556-4488-93de-9367f84e5ce8",
"created": 1670881947
}

View File

@ -1,5 +0,0 @@
{
"quote": "Bumsquatsch #1",
"source": "c5e0cbf4-1556-4488-93de-9367f84e5ce8",
"created": 1670881943
}

View File

@ -1 +0,0 @@
{"name":"Team Hogwards","lang":"en","created":1650831066}

View File

@ -1,4 +0,0 @@
{
"quote": "I'm friends with spiders.",
"source": "23c93faa-ac5b-4e37-bf32-4276aba682bb"
}

View File

@ -1,4 +0,0 @@
{
"quote": "My first magic wand - an exclusive model from designer Thoronus Karpes — was a gift from my father.",
"source": "15c03b7c-729f-4202-a880-3bcc7214dad9"
}

View File

@ -1,4 +0,0 @@
{
"quote": "My stupid brothers frequently test their latest invention on me.",
"source": "bff68447-513c-4fa3-9224-fc59d83da81a"
}

View File

@ -1,4 +0,0 @@
{
"quote": "Since first grade, I've been best in all subjects and courses without a single miss.",
"source": "99e1aa2e-6e37-43c7-809c-28f093e2ae81"
}

View File

@ -1,4 +0,0 @@
{
"quote": "My private collection has 4031 different potions.",
"source": "35ee06ca-82c7-4f23-9bb9-bd0943848b07"
}

View File

@ -1 +0,0 @@
{"name":"Team Hogwards","lang":"de","created":1649841591}

View File

@ -1,4 +0,0 @@
{
"quote": "Ich bin seit der ersten Klasse in allen Fächern Klassenbeste:r.",
"source": "de69fa89-2b55-4c78-8718-9084458ceada"
}

View File

@ -1,4 +0,0 @@
{
"quote": "Meine Privatsammlung umfasst 4031 verschiedene Zaubertränke.",
"source": "c885c77f-bbc8-4547-a3b4-baac5a58e76e"
}

View File

@ -1,4 +0,0 @@
{
"quote": "Meinen ersten Zauberstab — ein exklusives Modell von Edeldesigner Thoronus Karpes — hat mir mein Vater geschenkt.",
"source": "cf230e60-9e1e-4158-93eb-ca184e15a6af"
}

View File

@ -1,4 +0,0 @@
{
"quote": "Ich bin mit Spinnen befreundet.",
"source": "47ff4d04-b403-468f-b152-72226762e373"
}

View File

@ -1,4 +0,0 @@
{
"quote": "Meine blöden Brüder testen ständig ihre neusten Erfindungen an mir.",
"source": "fae6e837-d603-436d-ba0c-df6a850350c0"
}

View File

@ -1 +0,0 @@
{"authcode":"","name":"Draco","role":"player","game":"663576f0-1378-496b-a970-578bdcb222af","created":1650831166,"lastLoggedIn":0}

View File

@ -1 +0,0 @@
{"authcode":"805088","name":"Player #13","role":"player","game":"64efba47-87dc-4c19-851c-aa68c9f0e2c1","created":1651603645,"lastLoggedIn":1651603989}

View File

@ -1 +0,0 @@
{"authcode":"","name":"Hagrid","role":"player","game":"663576f0-1378-496b-a970-578bdcb222af","created":1650831162,"lastLoggedIn":0}

View File

@ -1 +0,0 @@
{"authcode":"","name":"Snape","role":"player","game":"663576f0-1378-496b-a970-578bdcb222af","created":1650831158,"lastLoggedIn":0}

View File

@ -1 +0,0 @@
{"authcode":"406234","name":"Player #06","role":"player","game":"64efba47-87dc-4c19-851c-aa68c9f0e2c1","created":1651603311,"lastLoggedIn":1651603989}

View File

@ -1 +0,0 @@
{"authcode":"098577","name":"Player #07","role":"player","game":"64efba47-87dc-4c19-851c-aa68c9f0e2c1","created":1651603340,"lastLoggedIn":1651603989}

View File

@ -1 +0,0 @@
{"authcode":"044843","name":"Player #05","role":"player","game":"64efba47-87dc-4c19-851c-aa68c9f0e2c1","created":1651603278,"lastLoggedIn":1651603989}

View File

@ -1,5 +0,0 @@
{
"authcode": "646162",
"name": "Settel (Admin)",
"role": "admin"
}

View File

@ -1 +0,0 @@
{"authcode":"606242","name":"Player #15","role":"player","game":"64efba47-87dc-4c19-851c-aa68c9f0e2c1","created":1651603679,"lastLoggedIn":1651603989}

View File

@ -1 +0,0 @@
{"authcode":"","name":"Hagrid","role":"player","game":"e24444aa-8a18-48aa-a36d-8f84620726f8","created":1649841804}

View File

@ -1 +0,0 @@
{"authcode":"920510","name":"Player #16","role":"player","game":"64efba47-87dc-4c19-851c-aa68c9f0e2c1","created":1651603703,"lastLoggedIn":1651603989}

Some files were not shown because too many files have changed in this diff Show More