Files
77th_eventcalenderntfy/src/component/event/events.ts
chiko eea37b3df5 Fixing the uid Column isn't unique.
uid is required to be unique for the the Changed Events (with the new Data) to be inserted without creating new Rows.
2025-11-03 00:22:01 +00:00

253 lines
8.3 KiB
TypeScript

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<TEventEntity, "event_uid">
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<TEvent[]> {
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( "<TODAY>" )
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;
}
}