Skip to content

Commit 3c5da97

Browse files
committed
feat(database): Added migration service, updated Client class to apply migrations and set up initial tables, modified database schema to add created_at and updated_at columns to User and Group tables, removed ApprovedUser and Blacklist tables, and updated seed data to match new schema
1 parent 8204e2d commit 3c5da97

5 files changed

Lines changed: 87 additions & 60 deletions

File tree

src/database/Client.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
import { ConnectionPool } from './ConnectionPool';
2-
import { TablesService } from './service/tables/Tables';
2+
import { MigrationService } from './service/Migration';
3+
import { TablesService } from './service/Tables';
34
export class Client {
45
private _connectionPool: ConnectionPool;
56
constructor() {
67
this._connectionPool = new ConnectionPool();
78
}
89
async initialize() {
910
await this._connectionPool.connect();
11+
const migrationService = new MigrationService(this._connectionPool);
12+
13+
console.log('Applying migrations...');
14+
await migrationService.applyMigrations(); // Apply pending migrations
15+
console.log('Migrations applied successfully.');
16+
1017
const tablesService = new TablesService(this._connectionPool);
11-
console.log('connect database');
18+
console.log('Setting up initial tables...');
1219
await tablesService.initialTables();
13-
console.log('Initial Tables');
20+
console.log('Initial Tables Setup Completed.');
21+
22+
console.log('Seeding tables...');
1423
await tablesService.seedTables();
15-
console.log('Initial Seed Tables');
24+
console.log('Tables seeded successfully.');
1625
}
1726
getConnectionPool(): ConnectionPool {
1827
return this._connectionPool;

src/database/ConnectionPool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Pool, PoolClient } from 'pg';
22
import Config from '../config';
3-
import { Catch } from '../decorators/ErrorHandlingDecorator';
3+
import { Catch } from '../decorators/Catch';
44
export class ConnectionPool {
55
private _pool: Pool;
66
constructor() {

src/database/service/Migration.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import * as path from 'path';
2+
import * as fs from 'fs/promises';
3+
import { ConnectionPool } from '../ConnectionPool';
4+
export class MigrationService {
5+
private migrationDir: string;
6+
7+
constructor(private _connectionPool: ConnectionPool) {
8+
this.migrationDir = path.join(__dirname, '..', 'migrations');
9+
}
10+
11+
// Check if the migration has been applied by its name
12+
async isMigrationApplied(migrationName: string): Promise<boolean> {
13+
const client = await this._connectionPool.getClient();
14+
const query = 'SELECT COUNT(*) FROM "Migration" WHERE name = $1';
15+
const result = await client.query(query, [migrationName]);
16+
return parseInt(result.rows[0].count, 10) > 0;
17+
}
18+
19+
// Mark the migration as applied
20+
async markMigrationAsApplied(migrationName: string): Promise<void> {
21+
const client = await this._connectionPool.getClient();
22+
const query = 'INSERT INTO "Migration" (name) VALUES ($1)';
23+
await client.query(query, [migrationName]);
24+
}
25+
26+
// Apply the migration files in sequence
27+
async applyMigrations() {
28+
const migrationFiles = await fs.readdir(this.migrationDir);
29+
// Sort files by filename to apply them in order (e.g., 001, 002, etc.)
30+
const sortedMigrationFiles = migrationFiles.sort();
31+
32+
for (const migrationFile of sortedMigrationFiles) {
33+
const migrationName = migrationFile.split('.')[0];
34+
35+
// Check if the migration has already been applied
36+
const isApplied = await this.isMigrationApplied(migrationName);
37+
if (!isApplied) {
38+
const filePath = path.join(this.migrationDir, migrationFile);
39+
const migrationSQL = await fs.readFile(filePath, 'utf-8');
40+
41+
const client = await this._connectionPool.getClient();
42+
await client.query(migrationSQL);
43+
44+
// Mark the migration as applied
45+
await this.markMigrationAsApplied(migrationName);
46+
console.log(`Migration ${migrationName} applied successfully.`);
47+
} else {
48+
console.log(`Migration ${migrationName} has already been applied.`);
49+
}
50+
}
51+
}
52+
}

src/database/sql/Tables.sql

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,24 @@ CREATE TABLE IF NOT EXISTS "User" (
66
first_name TEXT,
77
role TEXT CHECK (role IN ('admin','owner', 'user', 'approved_user')), -- User roles for managing permissions
88
warnings INT DEFAULT 0, -- Number of warnings the user has received
9-
approved_groups BIGINT[] -- Array of approved group IDs for the user
9+
approved_groups BIGINT[], -- Array of approved group IDs for the user
10+
created_at TIMESTAMP DEFAULT NOW(),
11+
updated_at TIMESTAMP DEFAULT NOW()
1012
);
1113

1214
-- Group table: Stores information about groups managed by the bot
1315
CREATE TABLE IF NOT EXISTS "Group" (
1416
id SERIAL PRIMARY KEY,
1517
group_id BIGINT UNIQUE NOT NULL, -- Telegram group ID
1618
group_name TEXT,
17-
rules TEXT[], -- Array of rules for the group
1819
black_list TEXT[], -- Array of blacklisted words or users
1920
chat_permissions JSONB, -- JSON to store permissions
2021
updated_at TIMESTAMP DEFAULT NOW(),
2122
joined_at TIMESTAMP DEFAULT NOW(),
2223
approved_users BIGINT[], -- Array of approved user IDs
2324
warnings BIGINT[], -- Number of warnings in the group
2425
is_spam_time BOOLEAN DEFAULT FALSE, -- Flag for spam detection
26+
welcome_message TEXT,
2527
members BIGINT[]
2628
);
2729

@@ -34,15 +36,6 @@ CREATE TABLE IF NOT EXISTS "Warning" (
3436
reason TEXT
3537
);
3638

37-
-- ApprovedUser table: Tracks users approved in specific groups
38-
CREATE TABLE IF NOT EXISTS "ApprovedUser" (
39-
id SERIAL PRIMARY KEY,
40-
user_id BIGINT REFERENCES "User"(id), -- User who was approved
41-
group_id BIGINT REFERENCES "Group"(id), -- Group where the user was approved
42-
username TEXT,
43-
approved_at TIMESTAMP DEFAULT NOW()
44-
);
45-
4639
-- Channel table: Stores channel details managed by the bot
4740
CREATE TABLE IF NOT EXISTS "Channel" (
4841
id SERIAL PRIMARY KEY,
@@ -51,28 +44,17 @@ CREATE TABLE IF NOT EXISTS "Channel" (
5144
admins BIGINT[] -- Array of admin user IDs for the channel
5245
);
5346

54-
-- Blacklist table: Stores blacklisted words and users for each group
55-
CREATE TABLE IF NOT EXISTS "Blacklist" (
56-
id SERIAL PRIMARY KEY,
57-
group_id BIGINT REFERENCES "Group"(id),
58-
blacklisted_word TEXT, -- Word to be blacklisted
59-
blacklisted_user_id BIGINT REFERENCES "User"(id) -- User to be blacklisted
60-
);
61-
6247
-- GroupRule table: Stores individual rules for each group
6348
CREATE TABLE IF NOT EXISTS "GroupRule" (
6449
id SERIAL PRIMARY KEY,
6550
group_id BIGINT REFERENCES "Group"(id),
66-
rule_text TEXT, -- Text of the rule
51+
rule_text TEXT[], -- Text of the rule
6752
added_at TIMESTAMP DEFAULT NOW(),
6853
added_by BIGINT REFERENCES "User"(id)
6954
);
70-
71-
-- GroupMessageSettings table: Stores group message settings (e.g., slow mode)
72-
CREATE TABLE IF NOT EXISTS "GroupMessageSettings" (
73-
id SERIAL PRIMARY KEY,
74-
group_id BIGINT REFERENCES "Group"(id),
75-
is_locked BOOLEAN DEFAULT FALSE,
76-
welcome_message TEXT,
77-
last_updated TIMESTAMP DEFAULT NOW()
78-
);
55+
-- migration table schema
56+
CREATE TABLE IF NOT EXISTS "Migration" (
57+
id SERIAL PRIMARY KEY,
58+
name TEXT UNIQUE NOT NULL,
59+
applied_at TIMESTAMP DEFAULT NOW()
60+
);
Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,27 @@
11
-- Seeding the User table
2-
INSERT INTO "User" (telegram_id, username, first_name,role, warnings, approved_groups)
2+
INSERT INTO "User" (telegram_id, username, first_name,role, warnings, approved_groups,created_at,updated_at)
33
VALUES
4-
(111222333, 'user1','fuser1' ,'admin', 0, ARRAY[123456789, 987654321]),
5-
(444555666, 'user2', 'fuser2','owner', 2, ARRAY[987654321]),
6-
(777888999, 'user3','fuser3' ,'user', 0, ARRAY[123456789]),
7-
(333444555, 'user4', 'fuser4','user', 1, ARRAY[123456789, 987654321]);
4+
(111222333, 'user1','fuser1' ,'admin', 0, ARRAY[123456789, 987654321],NOW(),NOW()),
5+
(444555666, 'user2', 'fuser2','owner', 2, ARRAY[987654321],NOW(),NOW()),
6+
(777888999, 'user3','fuser3' ,'user', 0, ARRAY[123456789],NOW(),NOW()),
7+
(333444555, 'user4', 'fuser4','user', 1, ARRAY[123456789, 987654321],NOW(),NOW());
88
-- Seeding the Group table
9-
INSERT INTO "Group" (group_id, group_name, rules, black_list, chat_permissions, updated_at, joined_at, approved_users, warnings, is_spam_time,members)
9+
INSERT INTO "Group" (group_id, group_name, black_list, chat_permissions, updated_at, joined_at, approved_users, warnings, is_spam_time,welcome_message,members)
1010
VALUES
11-
(123456789, 'Test Group 1', ARRAY['Rule 1', 'Rule 2'], ARRAY['BadWord1', 'BadWord2'], '{"can_post": true, "can_message": true}', NOW(), NOW(), ARRAY[1, 2], ARRAY[0], FALSE,ARRAY[0,2]),
12-
(987654321, 'Test Group 2', ARRAY['Rule A', 'Rule B'], ARRAY['OffensiveUser1'], '{"can_post": false, "can_message": false}', NOW(), NOW(), ARRAY[3, 4], ARRAY[1], TRUE,ARRAY[3,4]);
11+
(123456789, 'Test Group 1', ARRAY['BadWord1', 'BadWord2'], '{"can_post": true, "can_message": true}', NOW(), NOW(), ARRAY[1, 2], ARRAY[0], FALSE,'welcome message',ARRAY[0,2]),
12+
(987654321, 'Test Group 2', ARRAY['OffensiveUser1'], '{"can_post": false, "can_message": false}', NOW(), NOW(), ARRAY[3, 4], ARRAY[1], TRUE,'welcome message2',ARRAY[3,4]);
1313
-- Seeding the Warning table after Group data has been inserted
1414
INSERT INTO "Warning" (user_id, group_id, warned_at, reason)
1515
VALUES
1616
(1, 1, NOW(), 'Spamming'),
1717
(2, 2, NOW(), 'Offensive behavior');
18-
-- Seeding the ApprovedUser table
19-
INSERT INTO "ApprovedUser" (user_id, group_id, username)
20-
VALUES
21-
(1, 1, 'user1'),
22-
(2, 2, 'user2');
23-
2418
-- Seeding the Channel table
2519
INSERT INTO "Channel" (name, channel_id, admins)
2620
VALUES
2721
('Test Channel 1', 100010001, ARRAY[1, 2]),
2822
('Test Channel 2', 200020002, ARRAY[3, 4]);
29-
-- Seeding the Blacklist table
30-
INSERT INTO "Blacklist" (group_id, blacklisted_word, blacklisted_user_id)
31-
VALUES
32-
(1, 'BadWord1', NULL),
33-
(2, 'OffensiveUser1', 4);
3423
-- Seeding the GroupRule table
3524
INSERT INTO "GroupRule" (group_id, rule_text, added_by)
3625
VALUES
37-
(1, 'No spamming allowed', 1),
38-
(2, 'Be respectful to others', 2);
39-
-- Seeding the GroupMessageSettings table
40-
INSERT INTO "GroupMessageSettings" (group_id, is_locked, welcome_message)
41-
VALUES
42-
(1, FALSE, 'Welcome to the group!'),
43-
(2, TRUE, 'Group is locked, no messages allowed');
26+
(1, ARRAY['No spamming allowed'], 1),
27+
(2, ARRAY['Be respectful to others'], 2);

0 commit comments

Comments
 (0)