forked from TWS/kalkutago
Compare commits
6 commits
68ede48cbc
...
01398674f5
Author | SHA1 | Date | |
---|---|---|---|
D. Scott Boggs | 01398674f5 | ||
D. Scott Boggs | ac6a5e786c | ||
D. Scott Boggs | b81485a3d5 | ||
D. Scott Boggs | 9e16306c61 | ||
D. Scott Boggs | 741429630b | ||
D. Scott Boggs | e84e9e6594 |
60
README.md
Normal file
60
README.md
Normal 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
2
client/.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
dist/
|
10
client/Dockerfile
Normal file
10
client/Dockerfile
Normal 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/
|
||||
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
13
client/src/router.ts
Normal 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
|
|
@ -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
|
||||
|
|
11
client/src/views/TableView.vue
Normal file
11
client/src/views/TableView.vue
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue