15 Commits

Author SHA1 Message Date
c6ec442c2b Merge branch 'main' into dev 2025-11-06 20:16:54 +00:00
3e5032caf3 Merge branch 'dev' 2025-11-06 20:09:55 +00:00
2f805c0772 Bugfix: Added "deleted" as Notification State if Event deleteDate is set to 2025-11-06 20:08:08 +00:00
8aef42396e Merge branch 'version/0.1.5' 2025-11-06 17:48:10 +00:00
170695f9ff Version Bump: v0.1.5 2025-11-06 17:39:00 +00:00
c703911f85 Bugfix: Events marked as "removed" And deleteDate is set so the Notification does not get sent 2025-11-06 17:36:54 +00:00
a37d95709f Merge pull request 'version/0.1.4' (#9) from version/0.1.4 into main
Reviewed-on: #9
2025-11-03 00:41:45 +00:00
152c1bcba0 Version Bump 0.1.4 2025-11-03 00:35:40 +00:00
5cdfd0f2e3 Change the Start Script and Added NODE_ENV to the env vars also for the dockerized Version 2025-11-03 00:33:23 +00:00
1c6aad0f3a Merge pull request 'fix/db-event-uid-create-unique-index' (#8) from fix/db-event-uid-create-unique-index into dev
Reviewed-on: #8
2025-11-03 00:28:36 +00:00
16593e0281 Fixed Syntax Error 2025-11-03 00:24:42 +00:00
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
1a7de55da8 Prints "Optime" and a Diff to Optime for a Events thats not on Optime 2025-11-02 21:11:37 +00:00
2c34fece2c Merge pull request 'feature/notification-more-options' (#7) from feature/notification-more-options into dev
Reviewed-on: #7
2025-11-02 20:56:14 +00:00
ae569b7739 Merge pull request 'Bugfix in sendNotification()' (#6) from version/0.1.3 into main
Reviewed-on: #6
2025-10-27 16:55:12 +00:00
13 changed files with 112 additions and 21 deletions

View File

@@ -1,6 +1,6 @@
services: services:
app: app:
image: chiko/77th_eventcalendarntfy:v0.1.3 image: chiko/77th_eventcalendarntfy:v0.1.5
build: . build: .
volumes: volumes:
- ./data/db:/opt/app/data/db - ./data/db:/opt/app/data/db

View File

@@ -2,4 +2,3 @@ SHELL=/bin/bash
MAILTO="" MAILTO=""
0 8 * * * root . /etc/cron-env.sh && /opt/app/run-task.sh --today >> /proc/1/fd/1 2>&1 0 8 * * * root . /etc/cron-env.sh && /opt/app/run-task.sh --today >> /proc/1/fd/1 2>&1
*/15 * * * * root . /etc/cron-env.sh && /opt/app/run-task.sh >> /proc/1/fd/1 2>&1 */15 * * * * root . /etc/cron-env.sh && /opt/app/run-task.sh >> /proc/1/fd/1 2>&1
* * * * * root echo "cron test ran at $(date)" >> /proc/1/fd/1 2>&1

View File

@@ -7,6 +7,7 @@ chmod +x /etc/cron-env.sh
# Write the Env Vars into a file for cron. happens during runtime of the container and not build. # Write the Env Vars into a file for cron. happens during runtime of the container and not build.
# List your environment variables here # List your environment variables here
env_vars=( env_vars=(
NODE_ENV
TZ TZ
DB_FILEPATH DB_FILEPATH
DB_FILENAME DB_FILENAME

View File

@@ -1,5 +1,5 @@
{ {
"version": "0.1.3", "version": "0.1.5",
"name": "77th_eventcalendarnotification", "name": "77th_eventcalendarnotification",
"module": "./src/app.ts", "module": "./src/app.ts",
"type": "module", "type": "module",
@@ -16,7 +16,7 @@
"typescript-eslint": "^8.46.2" "typescript-eslint": "^8.46.2"
}, },
"scripts": { "scripts": {
"prod": "NODE_ENV=production bun run ./src/app.ts", "start": "bun run ./src/app.ts",
"dev": "NODE_ENV=development bun ./src/app.ts", "dev": "NODE_ENV=development bun ./src/app.ts",
"db:init": "bun run ./run/db_init.ts", "db:init": "bun run ./run/db_init.ts",
"db:deleteall": "bun run ./run/db_event_deleteall.ts", "db:deleteall": "bun run ./run/db_event_deleteall.ts",

View File

@@ -0,0 +1,7 @@
import db from "../src/sql";
db.run(
`UPDATE events
SET notification = 'done'
WHERE deleteDate IS NOT NULL;`
);

View File

@@ -0,0 +1,39 @@
import db from "../src/sql";
const run_migration = db.transaction(() => {
// SQL 1: remove duplicates by uid
db.run(`DELETE FROM events
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM events
GROUP BY uid
);`);
// SQL 2: create new table with unique key
db.run(`CREATE TABLE events_new (
"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
);`);
// SQL 3: Log the transaction
db.run(`INSERT INTO events_new (event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate)
SELECT event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate FROM events;
`);
db.run(`DROP TABLE events;
ALTER TABLE events_new RENAME TO events;`);
});
// Run the transaction
run_migration();

View File

@@ -0,0 +1 @@
CREATE UNIQUE INDEX idx_events_uid ON events(uid);

View File

@@ -0,0 +1,6 @@
DELETE FROM events
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM events
GROUP BY uid
);

View File

@@ -0,0 +1,4 @@
SELECT uid, COUNT(*) AS count
FROM events
GROUP BY uid
HAVING COUNT(*) > 1;

View File

@@ -0,0 +1,29 @@
DELETE FROM events
WHERE rowid NOT IN (
SELECT MIN(rowid)
FROM events
GROUP BY uid
);
CREATE TABLE events_new (
"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
);
INSERT INTO events_new (event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate)
SELECT event_uid, uid, title, date_at, time_start, time_end, posted_by, location, event_type, link, description, timezone, notification, deleteDate FROM events;
DROP TABLE events;
ALTER TABLE events_new RENAME TO events;

View File

@@ -19,10 +19,12 @@ async function events_update_db() {
console.log("events_fetched.length: " + events_fetched.length ); console.log("events_fetched.length: " + events_fetched.length );
const events_fetched_list_of_uids = events_fetched.map( event => { return event.uid; }); const events_fetched_list_of_uids = events_fetched.map( event => { return event.uid; });
console.dir({events_fetched_list_of_uids} ); console.dir( {events_fetched_list_of_uids} );
const events_db_currentMonth = Event.get_events({month: {year: TODAY.year, month: TODAY.month}}, db); const events_db_currentMonth = Event.get_events({month: {year: TODAY.year, month: TODAY.month}}, db);
const events_removed: Event[] = events_db_currentMonth.filter( (ev) => { const events_db_nextMonth = Event.get_events({month: {year: TODAY.year, month: (TODAY.month + 1)}}, db);
const events_db = [... events_db_currentMonth, ... events_db_nextMonth];
const events_removed: Event[] = events_db.filter( (ev) => {
return ! events_fetched_list_of_uids.includes(ev.uid); return ! events_fetched_list_of_uids.includes(ev.uid);
}); });
@@ -43,7 +45,7 @@ async function events_update_db() {
console.log("loop ev " + ev.uid + " : " + [ ev.title, ev.date_at ].join( ", " ) ); console.log("loop ev " + ev.uid + " : " + [ ev.title, ev.date_at ].join( ", " ) );
const found = AllRelevantEvents.find(event => event.uid === ev.uid); const found = AllRelevantEvents.find(event => event.uid === ev.uid);
if ( found ) { if ( found ) {
console.log("loop ev " + ev.uid + " found: " + [ found.title, found.date_at ].join( ", " ) ); console.log("loop ev " + ev.uid + " f: " + [ found.title, found.date_at ].join( ", " ) );
if ( if (
found.title != ev.title || found.title != ev.title ||
found.description != ev.description || found.description != ev.description ||
@@ -56,12 +58,12 @@ async function events_update_db() {
found.timezone != ev.timezone || found.timezone != ev.timezone ||
found.link != ev.link found.link != ev.link
) { ) {
console.log("loop ev " + ev.uid + " different (changed): " + [ ev.title, ev.date_at ].join( ", " ) ); console.log("loop ev " + ev.uid + " c: " + [ ev.title, ev.date_at ].join( ", " ) );
const newEventToInsert: TEventEntityNew = {... ev, notification: "changed"}; const newEventToInsert: TEventEntityNew = {... ev, notification: "changed"};
eventsToInsert.push( newEventToInsert ); eventsToInsert.push( newEventToInsert );
} }
} else { } else {
console.log("loop ev " + ev.uid + " added (new): " + [ ev.title, ev.date_at ].join( ", " ) ); console.log("loop ev " + ev.uid + " n: " + [ ev.title, ev.date_at ].join( ", " ) );
const newEventToInsert: TEventEntityNew = {... ev, notification: "new"}; const newEventToInsert: TEventEntityNew = {... ev, notification: "new"};
eventsToInsert.push( newEventToInsert ); eventsToInsert.push( newEventToInsert );
} }
@@ -100,9 +102,10 @@ async function events_check_for_notification() {
await sendNotification( ev.get_title(), ev.get_body(), notificationOptions ); await sendNotification( ev.get_title(), ev.get_body(), notificationOptions );
if ( ev.notification == "removed" ) { if ( ev.notification == "removed" ) {
ev.set_deleted( db ); ev.set_deleted( db );
} } else {
ev.set_notification("done", db); ev.set_notification("done", db);
} }
}
} }
async function main ( ) { async function main ( ) {

View File

@@ -19,7 +19,7 @@ export type TGetEventsOptions = {
} }
export type TEventEntity = TEvent & { export type TEventEntity = TEvent & {
event_uid: number event_uid: number
notification: "new" | "changed" | "removed" | "done" notification: "new" | "changed" | "removed" | "done" | "deleted"
} }
export type TEventEntityNew = Omit<TEventEntity, "event_uid"> export type TEventEntityNew = Omit<TEventEntity, "event_uid">
@@ -43,8 +43,8 @@ export class Event implements TEventEntity {
static createTable (db: Database): void { static createTable (db: Database): void {
const query = db.query(`CREATE TABLE IF NOT EXISTS "events" ( const query = db.query(`CREATE TABLE IF NOT EXISTS "events" (
"event_uid" INTEGER NOT NULL, "event_uid" INTEGER PRIMARY KEY,
"uid" TEXT NOT NULL, "uid" TEXT NOT NULL UNIQUE,
"title" TEXT NOT NULL, "title" TEXT NOT NULL,
"date_at" DATETIME NOT NULL, "date_at" DATETIME NOT NULL,
"time_start" TEXT NOT NULL, "time_start" TEXT NOT NULL,
@@ -56,10 +56,8 @@ export class Event implements TEventEntity {
"description" TEXT NOT NULL, "description" TEXT NOT NULL,
"timezone" TEXT NOT NULL, "timezone" TEXT NOT NULL,
"notification" TEXT NOT NULL, "notification" TEXT NOT NULL,
"deleteDate" INTEGER NULL, "deleteDate" INTEGER NULL
PRIMARY KEY ("event_uid") );`);
);
CREATE UNIQUE INDEX "sqlite_autoindex_events_1" ON "events" ("uid");`);
query.run(); query.run();
} }
@@ -91,12 +89,12 @@ export class Event implements TEventEntity {
return events; return events;
} }
static get_events (options: TGetEventsOptions, db: Database ) { static get_events ( options: TGetEventsOptions, db: Database ) {
const whereConditions: string[] = []; const whereConditions: string[] = [];
if ( options.notification ) { if ( options.notification ) {
whereConditions.push( `notification IN ('${ options.notification.join("', '") }')` ) whereConditions.push( `notification IN ('${ options.notification.join("', '") }')` )
} }
if (options.date) { if ( options.date ) {
whereConditions.push(`date_at = "${options.date.year}-${options.date.month}-${options.date.day}"`); whereConditions.push(`date_at = "${options.date.year}-${options.date.month}-${options.date.day}"`);
} }
if ( options.month ) { if ( options.month ) {
@@ -116,6 +114,7 @@ export class Event implements TEventEntity {
return null; return null;
})() })()
const query = db.query(`SELECT * FROM events${ where ? ( " " + where ) : ""};`).as(Event); const query = db.query(`SELECT * FROM events${ where ? ( " " + where ) : ""};`).as(Event);
console.dir({ db: { action: {get_events: query} } })
return query.all(); return query.all();
} }
@@ -186,7 +185,8 @@ export class Event implements TEventEntity {
set_deleted ( db: Database ) { set_deleted ( db: Database ) {
const query = db.prepare( const query = db.prepare(
`UPDATE events `UPDATE events
SET deleteDate = $deleteDate SET notification = 'deleted',
deleteDate = $deleteDate
WHERE event_uid = $event_uid;` WHERE event_uid = $event_uid;`
); );
query.get({ query.get({
@@ -220,7 +220,7 @@ export class Event implements TEventEntity {
const body = [ const body = [
`Title: ${this.title}`, `Title: ${this.title}`,
`Date: ${this.date_at}`, `Date: ${this.date_at}`,
`Time: ${this.get_time_start()}${ TimeDiff && TimeDiff == "00:00" ? ` (Optime ${TimeDiff})` : "" }`, `Time: ${this.get_time_start()} (OP Time${ TimeDiff != "00:00" ? ` ${TimeDiff}` : "" })`,
`Type: ${ TEventType[ this.event_type ] }`, `Type: ${ TEventType[ this.event_type ] }`,
`Location: ${this.location}`, `Location: ${this.location}`,
`By: ${this.posted_by}`, `By: ${this.posted_by}`,

View File

@@ -8,6 +8,8 @@ console.log(db_filepath);
export const db = new Database(db_filepath); export const db = new Database(db_filepath);
export default db;
export function init () { export function init () {
Event.createTable(db); Event.createTable(db);
} }