1
0
Fork 0
forked from TWS/kalkutago

Compare commits

...

6 commits

11 changed files with 123 additions and 27 deletions

60
README.md Normal file
View file

@ -0,0 +1,60 @@
# Kalkutago
A multi-user Tickmate clone for the web
## Development
### Before you start
The backend uses a postgres server which is not tracked as a part of the
repository, and neither is the password for it. You'll need to generate a
password by doing something like
```console
openssl rand -base64 36 > server/postgres.pw
```
The development environment uses Traefik to reverse-proxy the API and dev
server. In production, the API will serve the client directly, but this system
allows you to run the dev server behind a reverse proxy so that changes are
rendered quickly without having to rebuild the container. Using the vite dev
server also gets us better debugging in the browser.
However, that means that Traefik needs a hostname to listen for. The way I've
done this is by adding the following entry to my `/etc/hosts` file:
```
127.0.0.1 kalkutago
```
### Server
The server is written in Rust, using Rocket and SeaOrm. It uses Rust nightly, so
you'll need to `rustup override set nightly`, though it gets built in docker so
you really only need this for IDE support.
### Client
The client is a Vue application. It has a central state in
[`state.ts`](client/src/state.ts). On application load, the state is initialized
by fetching the current tracks and ticks via the API, and then subscribes to
updates from the server by opening an EventSource at `/api/v1/updates`.
The basic flow is:
- a user interaction happens which should trigger a state change
- the action is dispatched to the server (e.g. by making a PATCH request to
`/api/v1/tracks/<track_id>/ticked`)
- the server makes the appropriate change to the database
- an event is dispatched to the event sources subscribed via `/api/v1/updates`
- The client receives the event, the reactive state is updated, and the UI
changes to match the expected state.
### Running for development
`docker-compose_dev.yml` is symlinked to `docker-compose.yml` for now, so once
the hostname is set up Just run `docker-compose up --build` and visit
http://kalkutago/ to open the app
### Populating the database
In order to see the UI components I add a track to the database like
```console
curl --json '{"name": "test", "description": "test track", "icon": "❓", "enabled": 1}' kalkutago/api/v1/tracks
```

2
client/.dockerignore Normal file
View file

@ -0,0 +1,2 @@
node_modules/
dist/

10
client/Dockerfile Normal file
View file

@ -0,0 +1,10 @@
FROM node
# user node has UID 1000 in the container
USER node
EXPOSE 5173
VOLUME /client
WORKDIR /client
ADD package.json yarn.lock tsconfig.json tsconfig.node.json vite.config.ts /client/
RUN yarn
ADD public/ src/ index.html /client/

View file

@ -12,7 +12,8 @@
"dependencies": {
"bulma": "^0.9.4",
"sass": "^1.25.0",
"vue": "^3.2.47"
"vue": "^3.2.47",
"vue-router": "4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",

View file

@ -1,14 +1,7 @@
<script setup lang="ts">
import Table from "./components/Table.vue";
import { state } from "./state";
state.populate()
import { RouterView } from 'vue-router'
</script>
<template>
<div class="container">
<Table></Table>
</div>
<RouterView />
</template>
<style scoped></style>

View file

@ -1,6 +1,12 @@
import { createApp } from 'vue'
import './style.scss'
import App from './App.vue'
import router from './router'
import { state } from "./state";
import 'bulma/css/bulma.css'
import App from './App.vue'
createApp(App).mount('#app')
const app = createApp(App)
app.use(router)
app.mount('#app')
state.populate()

13
client/src/router.ts Normal file
View file

@ -0,0 +1,13 @@
import { createRouter, createWebHistory } from 'vue-router'
import TableView from './views/TableView.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: TableView }
// for other pages:
// {path: '/', component: import('./views/TableView.vue')}
]
})
export default router

View file

@ -18,12 +18,6 @@ export const state = reactive({
source.addEventListener('TickAdded', event => {
console.log(event)
const tick: Tick = JSON.parse(event.data)
// for (const track of this.tracks) {
// if (track.id === tick.track_id) {
// console.debug('pushing tick')
// track.ticks?.push(tick)
// }
// }
const tracks = this.tracks.map(track => {
if (track.id === tick.track_id) {
const ticks = track.ticks ?? []
@ -33,22 +27,17 @@ export const state = reactive({
}
return track
})
console.debug(tracks)
this.tracks = tracks
})
source.addEventListener('TickDropped', event => {
console.log(event)
const tick: Tick = JSON.parse(event.data)
// for (const track of this.tracks)
// if (track.id === tick.track_id)
// track.ticks = track.ticks?.filter($tick => $tick.id === tick.id)
const tracks = this.tracks.map(track => {
if (track.id === tick.track_id) {
track.ticks = track.ticks?.filter($tick => $tick.id !== tick.id)
}
return track
})
console.debug(tracks)
this.tracks = tracks
})
source.addEventListener('Lagged', event => {
@ -60,6 +49,7 @@ export const state = reactive({
error(event)
window.location = window.location
})
window.addEventListener('beforeunload', () => source.close())
},
async repopulate() {
this.state = State.Fetching

View file

@ -0,0 +1,11 @@
<script setup lang="ts">
import Table from "../components/Table.vue";
</script>
<template>
<div class="container">
<Table></Table>
</div>
</template>
<style scoped></style>

View file

@ -213,6 +213,11 @@
"@vue/compiler-dom" "3.3.4"
"@vue/shared" "3.3.4"
"@vue/devtools-api@^6.5.0":
version "6.5.0"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
"@vue/reactivity-transform@3.3.4":
version "3.3.4"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz#52908476e34d6a65c6c21cd2722d41ed8ae51929"
@ -522,6 +527,13 @@ vite@^4.3.9:
optionalDependencies:
fsevents "~2.3.2"
vue-router@4:
version "4.2.2"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.2.tgz#b0097b66d89ca81c0986be03da244c7b32a4fd81"
integrity sha512-cChBPPmAflgBGmy3tBsjeoe3f3VOSG6naKyY5pjtrqLGbNEXdzCigFUHgBvp9e3ysAtFtEx7OLqcSDh/1Cq2TQ==
dependencies:
"@vue/devtools-api" "^6.5.0"
vue-template-compiler@^2.7.14:
version "2.7.14"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz#4545b7dfb88090744c1577ae5ac3f964e61634b1"

View file

@ -37,11 +37,9 @@ services:
- ./db.mount:/var/lib/postgresql/data
client_devserver:
image: node
build: ./client
volumes: [ ./client:/client/ ]
working_dir: /client
command: [ "sh", "-c", "yarn && yarn dev --host 0.0.0.0" ]
expose: [ 5173 ]
command: "yarn dev --host 0.0.0.0"
networks: [ web ]
labels:
traefik.enable: true