diff --git a/client/package.json b/client/package.json index 7c844f3..f410ede 100644 --- a/client/package.json +++ b/client/package.json @@ -1,11 +1,12 @@ { - "name": "kalkulog-client", + "name": "kalkutago-client", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vue-tsc && vite build", + "watch": "vue-tsc --watch & sleep 3 && vite build --watch", "preview": "vite preview" }, "dependencies": { diff --git a/client/src/App.vue b/client/src/App.vue index 961954b..b7adb44 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,9 +1,14 @@ - + diff --git a/client/src/components/Table.vue b/client/src/components/Table.vue new file mode 100644 index 0000000..338bd0b --- /dev/null +++ b/client/src/components/Table.vue @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/client/src/components/TableRow.vue b/client/src/components/TableRow.vue new file mode 100644 index 0000000..840f0d4 --- /dev/null +++ b/client/src/components/TableRow.vue @@ -0,0 +1,18 @@ + + + diff --git a/client/src/components/TickComponent.vue b/client/src/components/TickComponent.vue new file mode 100644 index 0000000..7fc1ae0 --- /dev/null +++ b/client/src/components/TickComponent.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/client/src/error.ts b/client/src/error.ts new file mode 100644 index 0000000..5f2ab1a --- /dev/null +++ b/client/src/error.ts @@ -0,0 +1,6 @@ + +export const error = console.error + +export function handleError(message: string): any { + return (...args: any) => error(message, ...args) +} \ No newline at end of file diff --git a/client/src/state.ts b/client/src/state.ts new file mode 100644 index 0000000..e0ad4ad --- /dev/null +++ b/client/src/state.ts @@ -0,0 +1,93 @@ +import { reactive } from "vue" +import { Track } from "./track" +import { error } from "./error" + +enum State { + Unfetched, + Fetching, + Fetched, +} + +export const state = reactive({ + tracks: new Array, + state: State.Unfetched, + streamUpdatesFromServer() { + const source = new EventSource("/api/v1/updates") + source.addEventListener("open", () => console.debug("opened event source")) + source.addEventListener('message', event => console.log(event)) + 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 ?? [] + ticks.push(tick) + track.ticks = ticks + + } + 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 => { + console.log(event) + // Refresh the page, refetching the list of tracks and ticks + window.location = window.location + }) + source.addEventListener('error', event => { + error(event) + window.location = window.location + }) + }, + async repopulate() { + this.state = State.Fetching + this.tracks = await Track.fetchAll() + }, + async populate() { + if (this.state != State.Unfetched) return + await this.repopulate() + this.streamUpdatesFromServer() + this.state = State.Fetched + }, + async taskCompleted(track: Track, date: Date): Promise { + let query = new URLSearchParams() + query.append("year", date.getUTCFullYear().toString()) + query.append("month", (date.getUTCMonth() + 1).toString()) + // good thing I still had this ^^^^^^^^^^^^^^ in mind when I wrote this 😬 + query.append("day", date.getUTCDate().toString()) + const response: Response = await fetch(`/api/v1/tracks/${track.id}/ticked?${query.toString()}`, { method: "PATCH" }) + const body = await response.text() + if (!response.ok) { + error(body) + throw new Error(`error setting tick for track ${track.id} ("${track.name}"): ${response.status} ${response.statusText}`) + } + return JSON.parse(body) + }, + async taskMarkedIncomplete(track: Track) { + const { ok, status, statusText } = await fetch(`/api/v1/tracks/${track.id}/all-ticks`, { method: 'DELETE' }) + if (!ok) + error(`error deleting ticks for ${track.id}: ${statusText} (${status})`) + } +}) diff --git a/client/src/ticks.ts b/client/src/ticks.ts new file mode 100644 index 0000000..895682b --- /dev/null +++ b/client/src/ticks.ts @@ -0,0 +1,65 @@ +interface ITick { + id: number + track_id?: number + year?: number + month?: number + day?: number + hour?: number + minute?: number + second?: number + has_time_info?: number +} + +class Tick implements ITick { + id: number + track_id?: number + year?: number + month?: number + day?: number + hour?: number + minute?: number + second?: number + has_time_info?: number + + constructor(id: number, + track_id?: number, + year?: number, + month?: number, + day?: number, + hour?: number, + minute?: number, + second?: number, + has_time_info?: number, + ) { + this.id = id + this.track_id = track_id + this.year = year + this.month = month + this.day = day + this.hour = hour + this.minute = minute + this.second = second + this.has_time_info = has_time_info + } + + static fromJSON(tick: ITick): Tick { + return new Tick( + tick.id, + tick.track_id, + tick.year, + tick.month, + tick.day, + tick.hour, + tick.minute, + tick.second, + tick.has_time_info, + ) + } + + date(): Date | null { + if (this.year && this.month && this.day && this.hour && this.minute && this.second) { + return new Date(this.year!, this.month!, this.day, this.hour, this.minute, this.second) + } + return null + } +} diff --git a/client/src/track.ts b/client/src/track.ts new file mode 100644 index 0000000..14f7567 --- /dev/null +++ b/client/src/track.ts @@ -0,0 +1,100 @@ +import { error } from "./error" + +export interface ITrack { + id: number + name: String + description: String + icon: String + enabled: number + multiple_entries_per_day?: number + color?: number + order?: number + ticks?: Array +} + +export class Track implements ITrack { + id: number + name: String + description: String + icon: String + enabled: number + multiple_entries_per_day?: number + color?: number + order?: number + ticks?: Array + + constructor( + id: number, + name: String, + description: String, + icon: String, + enabled: number, + multiple_entries_per_day?: number, + color?: number, + order?: number, + ticks?: Array + ) { + this.id = id + this.name = name + this.description = description + this.icon = icon + this.enabled = enabled + this.multiple_entries_per_day = multiple_entries_per_day + this.color = color + this.order = order + this.ticks = ticks?.map(tick => Tick.fromJSON(tick)) + this.isSetOn = this.isSetOn.bind(this) + this.fetchTicks = this.fetchTicks.bind(this) + } + + static fromJSON(track: ITrack): Track { + return new Track(track.id, track.name, track.description, track.icon, track.enabled, track.multiple_entries_per_day, track.color, track.order) + } + + isSetOn(date: Date): boolean { + for (var tick of (this.ticks ?? [])) { + if ( + date.getUTCFullYear() == tick.year && + (date.getUTCMonth() + 1) == tick.month && + // Javascript Date ^^^ uses 0-index for dates of months 🤦 + date.getDate() == tick.day + ) return true + } + return false + } + + async fetchTicks(): Promise { + const response = await fetch(`/api/v1/tracks/${this.id}/ticks`) + if (response.ok) { + this.ticks = await response.json() + } else { + throw new Error(`error fetching ticks: ${response.statusText} (${response.status})`) + } + return this + } + + static async fetchAll(): Promise> { + const result = await fetch('/api/v1/tracks') + if (result.ok) { + try { + const body = await result.text(); + try { + const tracks = Array.prototype.map.call(JSON.parse(body), Track.fromJSON) as Array + return Promise.all(tracks.map((track: Track) => track.fetchTicks())) + } catch (e) { + console.error('error parsing body from JSON') + console.error(e) + console.debug(body) + } + } catch (err) { + console.error(err) + result.text() + .then(console.debug) + .catch(console.error) + } + } else { + error(`error fetching tracks: ${result.statusText} (${result.status})`) + } + return [] + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index f1e488e..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,43 +0,0 @@ -version: "3.5" - -services: - server: - # build: ./server - build: - context: ./server - dockerfile: Dockerfile.debug - networks: - - web - - internal - environment: - POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password - POSTGRES_USER: kalkulog - POSTGRES_DB: kalkulog - POSTGRES_HOST: database - secrets: [ postgres-password ] - depends_on: [ database ] - ports: - # TODO remove in prod - - 8000:8000 - volumes: - - ./client/dist:/src/public:ro - database: - image: postgres - environment: - POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password - POSTGRES_USER: kalkulog - POSTGRES_DB: kalkulog - secrets: [ postgres-password ] - networks: [ internal ] - volumes: - - ./db.mount:/var/lib/postgresql/data - -secrets: - postgres-password: - file: ./server/postgres.pw - -networks: - internal: - internal: true - web: - external: true diff --git a/docker-compose.yml b/docker-compose.yml new file mode 120000 index 0000000..9f1a63f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1 @@ +docker-compose_dev.yml \ No newline at end of file diff --git a/docker-compose_dev.yml b/docker-compose_dev.yml new file mode 100644 index 0000000..780b9be --- /dev/null +++ b/docker-compose_dev.yml @@ -0,0 +1,75 @@ +version: "3.5" + +services: + server: + # build: ./server + build: + context: ./server + dockerfile: Dockerfile.debug + networks: + - web + - internal + environment: + POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password + POSTGRES_USER: kalkutago + POSTGRES_DB: kalkutago + POSTGRES_HOST: database + secrets: [ postgres-password ] + depends_on: [ database ] + expose: [ 8000 ] + # ports: + # # TODO remove in prod + # - 8000:8000 + volumes: + - ./client/dist:/src/public:ro + labels: + traefik.enable: true + traefik.http.routers.kalkutago_server.rule: 'Host(`kalkutago`) && PathPrefix(`/api`)' + database: + image: postgres + environment: + POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password + POSTGRES_USER: kalkutago + POSTGRES_DB: kalkutago + secrets: [ postgres-password ] + networks: [ internal ] + volumes: + - ./db.mount:/var/lib/postgresql/data + + client_devserver: + image: node + volumes: [ ./client:/client/ ] + working_dir: /client + command: [ "sh", "-c", "yarn && yarn dev --host 0.0.0.0" ] + expose: [ 5173 ] + networks: [ web ] + labels: + traefik.enable: true + traefik.http.routers.kalkutago_client.rule: 'Host(`kalkutago`) && !PathPrefix(`/api`)' + traefik.http.services.kalkutago_client.loadbalancer.server.port: 5173 + + proxy: + image: traefik + volumes: + - source: /var/run/docker.sock + target: /var/run/docker.sock + type: bind + - source: ./traefik.yaml + target: /traefik.yaml + type: bind + - source: ./traefik-config + target: /config + type: bind + ports: + - 80:80 + networks: [ web ] + +secrets: + postgres-password: + file: ./server/postgres.pw + +networks: + internal: + internal: true + web: + external: true diff --git a/docker-compose_prod.yml b/docker-compose_prod.yml new file mode 100644 index 0000000..cb57656 --- /dev/null +++ b/docker-compose_prod.yml @@ -0,0 +1,40 @@ +version: "3.5" + +services: + server: + # build: ./server + build: + context: ./server + dockerfile: Dockerfile.debug + networks: + - web + - internal + environment: + POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password + POSTGRES_USER: kalkutago + POSTGRES_DB: kalkutago + POSTGRES_HOST: database + secrets: [ postgres-password ] + depends_on: [ database ] + volumes: + - ./client/dist:/src/public:ro + database: + image: postgres + environment: + POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password + POSTGRES_USER: kalkutago + POSTGRES_DB: kalkutago + secrets: [ postgres-password ] + networks: [ internal ] + volumes: + - ./db.mount:/var/lib/postgresql/data + +secrets: + postgres-password: + file: ./server/postgres.pw + +networks: + internal: + internal: true + web: + external: true diff --git a/server/Cargo.lock b/server/Cargo.lock index 22424ca..59054fd 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -36,6 +36,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "allocator-api2" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -47,9 +59,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "8868f09ff8cea88b079da74ae569d9b8c62a23c68c746240b704ee6f7525c89c" [[package]] name = "async-stream" @@ -70,7 +82,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -81,7 +93,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -155,9 +167,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" [[package]] name = "bitvec" @@ -279,14 +291,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", - "num-integer", + "js-sys", "num-traits", "serde", + "time 0.1.45", + "wasm-bindgen", "winapi", ] @@ -333,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" dependencies = [ "percent-encoding", - "time", + "time 0.3.22", "version_check", ] @@ -364,9 +379,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -473,11 +488,11 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -535,6 +550,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "erased-serde" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" +dependencies = [ + "serde", +] + [[package]] name = "errno" version = "0.3.1" @@ -543,7 +567,7 @@ checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -571,6 +595,22 @@ dependencies = [ "instant", ] +[[package]] +name = "femme" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc04871e5ae3aa2952d552dae6b291b3099723bf779a8054281c1366a54613ef" +dependencies = [ + "cfg-if", + "js-sys", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "figment" version = "0.10.10" @@ -593,9 +633,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -707,13 +747,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -760,12 +800,22 @@ dependencies = [ ] [[package]] -name = "hashlink" -version = "0.8.2" +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0761a1b9491c4f2e3d66aa0f62d0fba0af9a0e2852e4d48ea506632a4b56e6aa" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" dependencies = [ - "hashbrown 0.13.2", + "ahash 0.8.3", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +dependencies = [ + "hashbrown 0.14.0", ] [[package]] @@ -885,9 +935,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -914,9 +964,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -956,7 +1006,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -968,7 +1018,7 @@ dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -988,19 +1038,22 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] -name = "kalkulog-server" +name = "kalkutago-server" version = "0.1.0" dependencies = [ + "chrono", "derive_builder", "either", + "femme", + "log", "rocket", "sea-orm", "sea-orm-migration", @@ -1018,9 +1071,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "linux-raw-sys" @@ -1030,9 +1083,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1040,11 +1093,12 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "cfg-if", + "serde", + "value-bag", ] [[package]] @@ -1100,14 +1154,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", - "wasi", - "windows-sys 0.45.0", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] @@ -1192,15 +1245,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "ouroboros" @@ -1249,7 +1302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.8", ] [[package]] @@ -1268,15 +1321,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] @@ -1305,14 +1358,14 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" @@ -1367,9 +1420,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -1382,7 +1435,7 @@ checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", "version_check", "yansi", ] @@ -1409,9 +1462,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -1498,7 +1551,7 @@ checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -1612,7 +1665,7 @@ dependencies = [ "serde_json", "state", "tempfile", - "time", + "time 0.3.22", "tokio", "tokio-stream", "tokio-util", @@ -1632,7 +1685,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn 2.0.16", + "syn 2.0.18", "unicode-xid", ] @@ -1657,7 +1710,7 @@ dependencies = [ "smallvec", "stable-pattern", "state", - "time", + "time 0.3.22", "tokio", "uncased", ] @@ -1682,16 +1735,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.19" +version = "0.37.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1771,7 +1824,7 @@ dependencies = [ "serde_json", "sqlx", "thiserror", - "time", + "time 0.3.22", "tracing", "url", "uuid", @@ -1834,7 +1887,7 @@ dependencies = [ "rust_decimal", "sea-query-derive", "serde_json", - "time", + "time 0.3.22", "uuid", ] @@ -1850,7 +1903,7 @@ dependencies = [ "sea-query", "serde_json", "sqlx", - "time", + "time 0.3.22", "uuid", ] @@ -1920,22 +1973,31 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", +] + +[[package]] +name = "serde_fmt" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" +dependencies = [ + "serde", ] [[package]] @@ -2112,7 +2174,7 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", - "time", + "time 0.3.22", "tokio-stream", "url", "uuid", @@ -2186,9 +2248,77 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "sval" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2faba619276044eec7cd160d87b15d9191fb9b9f7198440343d2144f760cf08" + +[[package]] +name = "sval_buffer" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a353d3cca10721384077c9643c3fafdd6ed2600e57933b8e45c0b580d97b25af" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee5fc7349e9f6cb2ab950046818f66ad3f2d7209ccc5dced93da19292a30273a" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "098fb51d5d6007bd2c3f0a23b79aa953d7c46bf943086ce51424c3187c40f9b1" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f01126a2783d767496f18f13af26ab2587881f6343368bb26dc62956a723d1c7" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_ref" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5854d9eaa7bd31840a850322591c59c5b547eb29c9a6ecee1989d6ef963312ce" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cdd25fc04c5e882787d62112591aa93efb5bdc2000b43164d29f08582bb85f7" +dependencies = [ + "serde", + "sval", + "sval_buffer", + "sval_fmt", +] [[package]] name = "syn" @@ -2203,9 +2333,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -2229,7 +2359,7 @@ dependencies = [ "fastrand", "redox_syscall 0.3.5", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2255,7 +2385,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -2270,9 +2400,20 @@ dependencies = [ [[package]] name = "time" -version = "0.3.21" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" dependencies = [ "itoa", "serde", @@ -2312,9 +2453,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", @@ -2326,7 +2467,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -2337,7 +2478,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -2446,7 +2587,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", ] [[package]] @@ -2566,9 +2707,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", @@ -2577,9 +2718,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.3.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" dependencies = [ "serde", ] @@ -2590,6 +2731,42 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4735c95b4cca1447b448e2e2e87e98d7e7498f4da27e355cf7af02204521001d" +dependencies = [ + "erased-serde", + "serde", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859cb4f0ce7da6a118b559ba74b0e63bf569bea867c20ba457a6b1c886a04e97" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + [[package]] name = "version_check" version = "0.9.4" @@ -2606,6 +2783,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2614,34 +2797,36 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", + "serde", + "serde_json", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2649,28 +2834,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -2733,16 +2918,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -2751,22 +2927,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -2775,93 +2936,51 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" diff --git a/server/Cargo.toml b/server/Cargo.toml index 329ce44..eddfb63 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kalkulog-server" +name = "kalkutago-server" version = "0.1.0" edition = "2021" @@ -8,11 +8,14 @@ default = ["unsafe_import"] unsafe_import = [] [[bin]] -name = "kalkulog-server" +name = "kalkutago-server" path = "src/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = "0.4.26" +femme = "2.2.1" +log = { version = "0.4.19", features = ["kv_unstable", "kv_unstable_serde"] } sea-orm-migration = "0.11.3" serde_json = "1.0.96" thiserror = "1.0.40" diff --git a/server/Dockerfile b/server/Dockerfile index defbb05..8b2aec4 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -13,4 +13,4 @@ RUN rm dummy.rs &&\ sed -i "s:dummy.rs:src/main.rs:" Cargo.toml RUN cargo build --release # ADD public/ public/ -CMD ["target/release/kalkulog-server"] +CMD ["target/release/kalkutago-server"] diff --git a/server/Dockerfile.debug b/server/Dockerfile.debug index 04e659b..515b6a9 100644 --- a/server/Dockerfile.debug +++ b/server/Dockerfile.debug @@ -7,11 +7,11 @@ WORKDIR /src ADD Cargo.toml Cargo.lock /src/ RUN echo "fn main() {}" > dummy.rs &&\ sed -i "s:src/main.rs:dummy.rs:" Cargo.toml -RUN cargo build --release +RUN cargo build ADD src/ src/ RUN rm dummy.rs &&\ sed -i "s:dummy.rs:src/main.rs:" Cargo.toml -RUN cargo build --release +RUN cargo build # ADD public/ public/ -CMD ["target/release/kalkulog-server"] +CMD ["target/debug/kalkutago-server"] diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 24123a2..fc35abf 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -4,25 +4,56 @@ mod groups; mod import; mod ticks; mod tracks; +pub(crate) mod update; -use std::default::default; -use std::net::{IpAddr, Ipv4Addr}; +use std::{ + default::default, + net::{IpAddr, Ipv4Addr}, +}; use crate::error::Error; -use crate::rocket::{Build, Rocket}; -use rocket::fs::{FileServer, NamedFile}; -use rocket::{routes, Config}; +use rocket::{ + fs::{FileServer, NamedFile}, + response::stream::EventStream, + routes, Build, Config, Rocket, State, +}; use sea_orm::DatabaseConnection; pub(crate) use error::ErrorResponder; +use tokio::sync::broadcast::{self, error::RecvError, Sender}; -use self::error::ApiResult; +use self::{error::ApiResult, update::Update}; +use log::{as_debug, as_serde, debug, trace}; #[get("/status")] fn status() -> &'static str { "Ok" } +#[get("/updates")] +async fn stream_updates(tx: &State>) -> EventStream![] { + let mut rx = tx.subscribe(); + EventStream![loop { + let event = rx.recv().await; + match event { + Ok(update) => { + debug!(update = as_serde!(update); "sending update"); + let event = update.to_event(); + trace!(event = as_debug!(event); "this event"); + yield event; + } + Err(RecvError::Closed) => { + warn!("channel closed, ending update stream"); + break; + } + Err(RecvError::Lagged(count)) => { + warn!(count = count; "receiver lagged, instructing client to refresh"); + yield Update::lagged(count).to_event(); + } + } + }] +} + #[catch(404)] async fn spa_index_redirect() -> ApiResult { Ok(NamedFile::open("/src/public/index.html") @@ -34,6 +65,7 @@ pub(crate) fn start_server(db: DatabaseConnection) -> Rocket { use groups::*; use ticks::*; use tracks::*; + let (tx, _) = broadcast::channel::(8); let it = rocket::build() .configure(Config { address: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), @@ -41,7 +73,8 @@ pub(crate) fn start_server(db: DatabaseConnection) -> Rocket { }) .register("/", catchers![spa_index_redirect]) .manage(db) - .mount("/api/v1", routes![status]) + .manage(tx) + .mount("/api/v1", routes![status, stream_updates]) .mount( "/api/v1/tracks", routes![ @@ -50,7 +83,10 @@ pub(crate) fn start_server(db: DatabaseConnection) -> Rocket { ticks_for_track, insert_track, update_track, - delete_track + delete_track, + ticked, + ticked_on_date, + clear_all_ticks, ], ) .mount( diff --git a/server/src/api/ticks.rs b/server/src/api/ticks.rs index 6249f30..57905e7 100644 --- a/server/src/api/ticks.rs +++ b/server/src/api/ticks.rs @@ -1,13 +1,14 @@ use either::{Either, Left, Right}; use rocket::{http::Status, serde::json::Json, State}; use sea_orm::{prelude::*, DatabaseConnection}; +use tokio::sync::broadcast::Sender; use crate::{ entities::{prelude::*, *}, error::Error, }; -use super::error::ApiResult; +use super::{error::ApiResult, update::Update}; #[get("/")] pub(super) async fn all_ticks( @@ -59,10 +60,18 @@ pub(super) async fn update_tick( } #[delete("/")] -pub(super) async fn delete_tick(db: &State, id: i32) -> ApiResult { - Tracks::delete_by_id(id) - .exec(db as &DatabaseConnection) - .await - .map_err(Error::from)?; - Ok(Status::Ok) +pub(super) async fn delete_tick( + db: &State, + tx: &State>, + id: i32, +) -> ApiResult { + let db = db as &DatabaseConnection; + let tick = Ticks::find_by_id(id).one(db).await.map_err(Error::from)?; + if let Some(tick) = tick { + tick.clone().delete(db).await.map_err(Error::from)?; + tx.send(Update::tick_cancelled(tick)).map_err(Error::from)?; + Ok(Status::Ok) + } else { + Ok(Status::NotFound) + } } diff --git a/server/src/api/tracks.rs b/server/src/api/tracks.rs index 61d8121..a292ea7 100644 --- a/server/src/api/tracks.rs +++ b/server/src/api/tracks.rs @@ -4,8 +4,11 @@ use crate::error::Error; use either::Either::{self, Left, Right}; use rocket::http::Status; use rocket::{serde::json::Json, State}; -use sea_orm::{prelude::*, DatabaseConnection, EntityOrSelect}; +use sea_orm::{prelude::*, DatabaseConnection}; use std::default::default; +use tokio::sync::broadcast::Sender; + +use super::update::Update; #[get("/")] pub(super) async fn all_tracks( @@ -53,11 +56,9 @@ pub(super) async fn insert_track( db: &State, track: Json, ) -> ApiResult> { - let mut track = track.0; + let track = track.0; let db = db as &DatabaseConnection; let mut model: tracks::ActiveModel = default(); - track["id"] = 0.into(); // dummy value. set_from_json doesn't use this value - // but for some reason requires it be set model.set_from_json(track).map_err(Error::from)?; Ok(Json(model.insert(db).await.map_err(Error::from)?)) } @@ -86,3 +87,66 @@ pub(super) async fn delete_track(db: &State, id: i32) -> Api .map_err(Error::from)?; Ok(Status::Ok) } + +#[patch("//ticked")] +pub(super) async fn ticked( + db: &State, + tx: &State>, + id: i32, +) -> ApiResult> { + let tick = ticks::ActiveModel::now(id); + let tick = tick + .insert(db as &DatabaseConnection) + .await + .map_err(Error::from)? + .to_owned(); + tx.send(Update::tick_added(tick.clone())) + .map_err(Error::from)?; + Ok(Json(tick)) +} + +#[patch("//ticked?&&")] +pub(super) async fn ticked_on_date( + db: &State, + tx: &State>, + id: i32, + year: i32, + month: u32, + day: u32, +) -> ApiResult, Status>> { + let Some(date) = Date::from_ymd_opt(year, month, day) else { + return Ok(Right(Status::BadRequest)); + }; + let tick = ticks::ActiveModel::on(date, id); + let tick = tick + .insert(db as &DatabaseConnection) + .await + .map_err(Error::from)? + .to_owned(); + tx.send(Update::tick_added(tick.clone())) + .map_err(Error::from)?; + Ok(Left(Json(tick))) +} + +#[delete("//all-ticks")] +pub(super) async fn clear_all_ticks( + db: &State, + tx: &State>, + id: i32, +) -> ApiResult>>> { + let db = db as &DatabaseConnection; + let Some(track) = Tracks::find_by_id(id).one(db).await.map_err(Error::from)? else { + info!(track_id = id; "couldn't drop all ticks for track; track not found"); + return Ok(Left(Status::NotFound)); + }; + let ticks = track + .find_related(Ticks) + .all(db) + .await + .map_err(Error::from)?; + for tick in ticks.clone() { + tick.clone().delete(db).await.map_err(Error::from)?; + Update::tick_cancelled(tick).send(&tx)?; + } + Ok(Right(Json(ticks))) +} diff --git a/server/src/api/update.rs b/server/src/api/update.rs new file mode 100644 index 0000000..0b6adba --- /dev/null +++ b/server/src/api/update.rs @@ -0,0 +1,67 @@ +use log::as_serde; +use rocket::response::stream::Event; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use tokio::sync::broadcast::Sender; + +use crate::{entities::ticks, error::Result}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Update { + TickChanged { + kind: UpdateType, + tick: ticks::Model, + }, + Lagged { + kind: UpdateType, + count: u64, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum UpdateType { + TickAdded, + TickDropped, + Error, +} + +impl Update { + pub fn lagged(count: u64) -> Update { + Update::Lagged { + kind: UpdateType::Error, + count, + } + } + + pub fn tick_added(tick: ticks::Model) -> Self { + Self::TickChanged { + kind: UpdateType::TickAdded, + tick, + } + } + + pub fn tick_cancelled(tick: ticks::Model) -> Self { + Self::TickChanged { + kind: UpdateType::TickDropped, + tick, + } + } + + pub fn to_event(&self) -> Event { + use Update::*; + match self { + TickChanged { kind, tick } => Event::json(tick).event(format!("{kind:?}")), + Lagged { kind, count } => { + Event::json(&json! {{"message": "error: lagged", "count": count}}) + .event(format!("{kind:?}")) + } + } + } + + pub fn send(self, tx: &Sender) -> Result<()> { + let count = tx.send(self.clone())?; + trace!(sent_to = count, update = as_serde!(self); "sent update to SSE channel"); + Ok(()) + } +} diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index 03c66e7..b141c4f 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -45,7 +45,7 @@ fn get_env_var_or_file>(key: A) -> Option { /// Connect to the database using environment variables for configuration. /// Panics on any failure. -pub(crate) fn connection_url() -> String { +pub fn connection_url() -> String { let user = get_env_var_or_file("POSTGRES_USER").expect("$POSTGRES_USER"); let password = get_env_var_or_file("POSTGRES_PASSWORD").expect("$POSTGRES_PASSWORD"); let db = get_env_var_or_file("POSTGRES_DB").expect("$POSTGRES_DB"); diff --git a/server/src/entities/ticks.rs b/server/src/entities/ticks.rs index e2a4914..41d4107 100644 --- a/server/src/entities/ticks.rs +++ b/server/src/entities/ticks.rs @@ -1,5 +1,8 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 +use std::default::default; + +use chrono::{Datelike, Timelike, Utc}; use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -7,6 +10,7 @@ use serde::{Deserialize, Serialize}; #[sea_orm(table_name = "ticks")] pub struct Model { #[sea_orm(primary_key)] + #[serde(skip_deserializing)] pub id: i32, pub track_id: Option, pub year: Option, @@ -37,3 +41,46 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} + +impl ActiveModel { + pub(crate) fn now(track_id: i32) -> Self { + use sea_orm::ActiveValue::Set; + let now = Utc::now(); + Self { + track_id: Set(Some(track_id)), + year: Set(Some(now.year())), + month: Set(now.month().try_into().ok()), + /* ^^^^^^^^^^^^^^^^^^^^^^^ + * I can't imagine a situation where this doesn't fit. This way, at + * least, if it fails, you just get a messed up database entry that + * doesn't do anything bad + */ + day: Set(now.day().try_into().ok()), + hour: Set(now.hour().try_into().ok()), + minute: Set(now.minute().try_into().ok()), + second: Set(now.second().try_into().ok()), + has_time_info: Set(Some(1)), + ..default() + } + } + pub(crate) fn on(date: Date, track_id: i32) -> Self { + use sea_orm::ActiveValue::Set; + let now = Utc::now(); + Self { + track_id: Set(Some(track_id)), + year: Set(Some(date.year())), + month: Set(date.month().try_into().ok()), + /* ^^^^^^^^^^^^^^^^^^^^^^^ + * I can't imagine a situation where this doesn't fit. This way, at + * least, if it fails, you just get a messed up database entry that + * doesn't do anything bad + */ + day: Set(date.day().try_into().ok()), + hour: Set(now.hour().try_into().ok()), + minute: Set(now.minute().try_into().ok()), + second: Set(now.second().try_into().ok()), + has_time_info: Set(Some(1)), + ..default() + } + } +} diff --git a/server/src/entities/tracks.rs b/server/src/entities/tracks.rs index 0791db6..de4cef4 100644 --- a/server/src/entities/tracks.rs +++ b/server/src/entities/tracks.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; #[sea_orm(table_name = "tracks")] pub struct Model { #[sea_orm(primary_key)] + #[serde(skip_deserializing)] pub id: i32, pub name: String, pub description: String, diff --git a/server/src/error.rs b/server/src/error.rs index 19c4f1a..fbf5c10 100644 --- a/server/src/error.rs +++ b/server/src/error.rs @@ -16,6 +16,8 @@ pub enum Error { Unreachable, #[error(transparent)] Utf8(#[from] string::FromUtf8Error), + #[error(transparent)] + ChannelSendError(#[from] tokio::sync::broadcast::error::SendError), } pub type Result = std::result::Result; diff --git a/server/src/main.rs b/server/src/main.rs index 076f123..8959895 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -12,6 +12,7 @@ use sea_orm_migration::prelude::*; #[launch] async fn rocket_defines_the_main_fn() -> _ { + femme::with_level(femme::LevelFilter::Debug); let url = db::connection_url(); let db = Database::connect(url).await.expect("db connection"); let schema_manager = SchemaManager::new(&db); diff --git a/traefik-config/traefik_dynamic.yaml b/traefik-config/traefik_dynamic.yaml new file mode 100644 index 0000000..3556cab --- /dev/null +++ b/traefik-config/traefik_dynamic.yaml @@ -0,0 +1,5 @@ + routers: + api: + rule: Host(`traefik-monitor`) + entrypoints: [web] + service: api@internal diff --git a/traefik.yaml b/traefik.yaml new file mode 100644 index 0000000..f49b9c6 --- /dev/null +++ b/traefik.yaml @@ -0,0 +1,20 @@ +entrypoints: + web: + address: :80 + +api: + dashboard: true + +providers: + docker: + watch: true + network: web + exposedByDefault: false + file: + filename: /config/traefik_dynamic.yaml + +log: + level: INFO + format: json + +