import { error } from "./error" import { Tick, ITick } from './ticks' import { dateQuery } from "./util" 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 | undefined, 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) } /** * Add this track to the database. A `TrackAdded` event should have been * received from the server on the event stream by the time this returns. * * @returns whether or not the query succeeded */ async create(): Promise { // note that this.id is expected to be `undefined` here. const response = await fetch('/api/v1/tracks', { method: "POST", body: JSON.stringify(this), headers: { "Content-Type": "application/json" } }) if (!response.ok) error(`error submitting track ${this.name}: ${response.statusText} (${response.status})`) return response.ok } async delete() { const id = this.id if (id) await Track.deleteById(id) } static async deleteById(id: number) { const response = await fetch(`/api/v1/tracks/${id}`, { method: "DELETE" }) if (!response.ok) error(`error deleting track with ID ${id}: ${response.statusText} (${response.status})`) } 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 [] } /** * Mark this track as being completed on the given date. A `TickAdded` event * should have been received from the server on the event stream by the time * this returns. * * @param date the date the task was completed * @returns the decoded server API response */ async markComplete(date: Date) { const query = dateQuery(date) const response: Response = await fetch(`/api/v1/tracks/${this.id}/ticked?${query.toString()}`, { method: "PATCH" }) const body = await response.text() if (!response.ok) { error(body) throw new Error(`error setting tick for track ${this.id} ("${this.name}"): ${response.status} ${response.statusText}`) } return JSON.parse(body) } /** * Mark this track as being incomplete on the given date. A `TickAdded` event * should have been received from the server on the event stream by the time * this returns. * * @param date the date the task was completed * @returns the decoded server API response */ async markIncomplete(date: Date) { const query = dateQuery(date) const { ok, status, statusText } = await fetch(`/api/v1/tracks/${this.id}/all-ticks?${query.toString()}`, { method: 'DELETE' }) if (!ok) error(`error deleting ticks for ${this.id}: ${statusText} (${status})`) } }