import { Database } from "bun:sqlite"; import { TEventType, type TEvent } from "./event.types"; import { getTsNow, pad_l2, transformArray, formatTimeDiff, isEuropeanDST, subtractHours } from "../../util"; const BASE_URL = "https://77th-jsoc.com/service.php?action=get_events"; export type TGetEventsOptions = { notification?: TEventEntity["notification"][] | null, date?: { year: number, month: number, day: number }, month?: { year: number, month: number, }, deleted?: boolean } export type TEventEntity = TEvent & { event_uid: number notification: "new" | "changed" | "removed" | "done" } export type TEventEntityNew = Omit export class Event implements TEventEntity { static table_name: "events" event_uid: number; uid: string; title: string; description: string; date_at: string; time_start: string; time_end: string; posted_by: string; location: string; event_type: TEventEntity["event_type"]; timezone: string; link: string; notification: TEventEntity["notification"]; deleteDate: TEventEntity["deleteDate"]; static createTable (db: Database): void { const query = db.query(`CREATE TABLE IF NOT EXISTS "events" "event_uid" INTEGER PRIMARY KEY, "uid" TEXT NOT NULL UNIQUE, "title" TEXT NOT NULL, "date_at" DATETIME NOT NULL, "time_start" TEXT NOT NULL, "time_end" TEXT NOT NULL, "posted_by" TEXT NOT NULL, "location" TEXT NOT NULL, "event_type" TEXT NOT NULL, "link" TEXT NOT NULL, "description" TEXT NOT NULL, "timezone" TEXT NOT NULL, "notification" TEXT NOT NULL, "deleteDate" INTEGER NULL );`); query.run(); } static insert ( events: TEventEntityNew[], db: Database ) { const insert = db.prepare( [ "INSERT OR REPLACE INTO events", "(uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification)", "VALUES", "($uid, $title, $date_at, $time_start, $time_end, $posted_by, $location, $event_type, $link, $description, $timezone, $notification)" ].join(" ")); const insertEvents = db.transaction(events => { for (const event of events) insert.run(event); return events.length; }); const transforedEventArray = transformArray( events ); const count = insertEvents(transforedEventArray); console.log(`Inserted ${count} events`); } static async fetch_events( _year_: number, _month_: number, timezone: number): Promise { const url = `${BASE_URL}&year=${_year_}&month=${_month_}&timezone=${timezone}` const response = await fetch(url, { method: "GET", }); const body = await response.json() as {events: TEvent[] }; const events = body.events.sort( ( a, b ) => ( new Date(a.date_at) < new Date(b.date_at ) ) ? -1 : 1 ); return events; } static get_events (options: TGetEventsOptions, db: Database ) { const whereConditions: string[] = []; if ( options.notification ) { whereConditions.push( `notification IN ('${ options.notification.join("', '") }')` ) } if (options.date) { whereConditions.push(`date_at = "${options.date.year}-${options.date.month}-${options.date.day}"`); } if ( options.month ) { whereConditions.push( `strftime('%Y-%m', date_at) = '${options.month.year}-${options.month.month}'`) } const where = ( () => { let str = "WHERE "; if ( options.deleted === true ) { str += "deleteDate IS NOT NULL AND "; } else if ( options.deleted === false ) { str += "deleteDate IS NULL AND "; } if ( whereConditions.length >= 1 ) { return str += `( ${ whereConditions.join(" OR ") } )`; } return null; })() const query = db.query(`SELECT * FROM events${ where ? ( " " + where ) : ""};`).as(Event); return query.all(); } constructor(event_uid: number, uid: string, title: string, description: string, date_at: string, time_start: string, time_end: string, posted_by: string, location: string, event_type: TEventEntity["event_type"], timezone: string, link: string, notification: TEventEntity["notification"], deleteDate: TEventEntity["deleteDate"]) { this.event_uid = event_uid; this.uid = uid; this.title = title; this.description = description; this.date_at = date_at; this.time_start = time_start; this.time_end = time_end; this.posted_by = posted_by; this.location = location; this.event_type = event_type; this.timezone = timezone; this.link = link; this.notification = notification; this.deleteDate = deleteDate; } toString() { return { event_uid: this.event_uid, uid: this.uid, title: this.title, description: this.description, date_at: this.date_at, time_start: this.time_start, time_end: this.time_end, posted_by: this.posted_by, location: this.location, event_type: this.event_type, timezone: this.timezone, link: this.link, notification: this.notification, deleteDate: this.deleteDate } } syncWithDb ( db: Database ) { const query = db.prepare( `SELECT * FROM events WHERE event_uid = $event_uid;`).as(Event); const entity = query.get({$event_uid: this.event_uid }); if ( ! entity ) { throw new Error(`Could not find Event with event_uid ${this.event_uid} in DB!`); } this.uid = entity.uid; this.title = entity.title; this.description = entity.description; this.date_at = entity.date_at; this.time_start = entity.time_start; this.time_end = entity.time_end; this.posted_by = entity.posted_by; this.location = entity.location; this.event_type = entity.event_type; this.timezone = entity.timezone; this.link = entity.link; this.notification = entity.notification; this.deleteDate = entity.deleteDate; return this; } set_notification ( newValue: TEventEntity["notification"], db: Database ) { const query = db.prepare( `UPDATE events SET notification = $notification WHERE event_uid = $event_uid;` ); query.get({$notification: newValue, $event_uid: this.event_uid }); return this.syncWithDb( db ); } set_deleted ( db: Database ) { const query = db.prepare( `UPDATE events SET deleteDate = $deleteDate WHERE event_uid = $event_uid;` ); query.get({ $deleteDate: Math.floor((new Date()).getTime() / 1000), $event_uid: this.event_uid }); return this.syncWithDb( db ); } get_title() { const type_of_notification = ( (event: Event) => { switch ( event.notification ) { case "new": return "New"; case "changed": return "Changed"; case "removed": return "Removed"; default: return null; } } ) ( this ); const title_prefix_arr = []; if ( type_of_notification ) title_prefix_arr.push( "<" + type_of_notification + ">" ); if ( this.isEventToday() ) title_prefix_arr.push( "" ) return `${title_prefix_arr.length >= 1 ? ( title_prefix_arr.join(" " ) + " - ") : "" }${this.title} (${ TEventType[ this.event_type ] })`; } get_body() { const BaseTime = new Date(`${this.date_at} 21:00`); const RelativeEventTime = new Date(`${this.date_at} ${this.get_time_start()}`); const TimeDiff = formatTimeDiff( BaseTime, RelativeEventTime); const body = [ `Title: ${this.title}`, `Date: ${this.date_at}`, `Time: ${this.get_time_start()} (OP Time${ TimeDiff != "00:00" ? ` ${TimeDiff}` : "" })`, `Type: ${ TEventType[ this.event_type ] }`, `Location: ${this.location}`, `By: ${this.posted_by}`, `Link: ${this.link}`, ].join("\n"); return body; } isEventToday ( ) { const now = getTsNow(); const [year, month, day] = this.date_at.split("-") if ( year == String(now.year) && month == pad_l2( String(now.month) ) && day == pad_l2( String( now.day ) ) ) { return true; } return false; } get_time_start () { const date = new Date( `${this.date_at} ${this.time_start}` ); if ( ! isEuropeanDST( date ) ) { const newDate = subtractHours( date, 1); const hours = newDate.getHours(); const minutes = newDate.getMinutes(); return `${pad_l2(hours)}:${pad_l2(minutes)}`; } return this.time_start; } }