diff --git a/api/package.json b/api/package.json index b720034..b2bf7e6 100644 --- a/api/package.json +++ b/api/package.json @@ -6,13 +6,14 @@ "dev": "BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=1 bun with-env bun --watch src/index.ts", "start": "bun with-env bun src/index.ts", "typecheck": "tsc --noEmit", - "db:push": "bun with-env drizzle-kit push", + "db:push": "bun with-env drizzle-kit push && bun with-env bun src/utils/db/postpush.ts", "db:studio": "bun with-env drizzle-kit studio", "with-env": "dotenv -e ../.env --" }, "dependencies": { "@hono/zod-openapi": "^0.15.1", "@hono/zod-validator": "^0.2.2", + "cron": "^3.1.7", "drizzle-orm": "^0.32.1", "hono": "^4.5.11", "postgres": "^3.4.4", diff --git a/api/src/analytics.ts b/api/src/analytics.ts new file mode 100644 index 0000000..2a348ca --- /dev/null +++ b/api/src/analytics.ts @@ -0,0 +1,24 @@ +import { CronJob } from "cron"; +import { db } from "./utils/db"; +import { analytics } from "./utils/db/schema"; + +export async function createAnalyticsCronJob() { + new CronJob( + "* * * * *", + async () => { + const currentDate = new Date(); + const channels = await db.query.channels.findMany(); + await db.insert(analytics).values( + channels.map((channel) => ({ + timestamp: currentDate, + guildId: channel.guildId, + channelId: channel.id, + count: channel.count, + })) + ); + }, + null, + true, + "Africa/Abidjan" + ); +} diff --git a/api/src/index.ts b/api/src/index.ts index efe7da8..f63224f 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,8 +1,11 @@ import { OpenAPIHono } from "@hono/zod-openapi"; import { guildsRouter } from "./router/guilds"; import { channelsRouter } from "./router/channels"; +import { createAnalyticsCronJob } from "./analytics"; import { cors } from "hono/cors"; +createAnalyticsCronJob(); + const app = new OpenAPIHono(); app.use( diff --git a/api/src/utils/db/postpush.ts b/api/src/utils/db/postpush.ts new file mode 100644 index 0000000..fd83140 --- /dev/null +++ b/api/src/utils/db/postpush.ts @@ -0,0 +1,17 @@ +import { sql } from "drizzle-orm"; +import { db } from "."; + +async function main() { + try { + await db.execute( + sql`SELECT create_hypertable('analytics', by_range('timestamp'))` + ); + } catch (err) { + console.log( + "An error occured while creating hypertable. Hypertable probably already exists." + ); + } + process.exit(0); +} + +main(); diff --git a/api/src/utils/db/schema.ts b/api/src/utils/db/schema.ts index 7ac0b78..0baa210 100644 --- a/api/src/utils/db/schema.ts +++ b/api/src/utils/db/schema.ts @@ -1,5 +1,12 @@ import { relations } from "drizzle-orm"; -import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core"; +import { + boolean, + integer, + pgEnum, + pgTable, + text, + timestamp, +} from "drizzle-orm/pg-core"; export const guilds = pgTable("guilds", { id: text("id").primaryKey(), @@ -35,3 +42,14 @@ export const channelRelations = relations(channels, ({ one }) => ({ references: [guilds.id], }), })); + +export const analytics = pgTable("analytics", { + timestamp: timestamp("timestamp", { withTimezone: true }).primaryKey(), + guildId: text("guild_id") + .notNull() + .references(() => guilds.id), + channelId: text("channel_id") + .notNull() + .references(() => channels.id), + count: integer("count").notNull().default(0), +}); diff --git a/bun.lockb b/bun.lockb index 42c7ad7..5e43d3d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docker-compose.yml b/docker-compose.yml index 2866967..a351807 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: db: - image: postgres:16 + image: timescale/timescaledb-ha:pg16 restart: always environment: POSTGRES_PASSWORD: ${DATABASE_PASSWORD}