@2byte/tgbot-framework 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -300
- package/bin/2byte-cli.ts +97 -97
- package/package.json +6 -5
- package/src/cli/CreateBotCommand.ts +181 -181
- package/src/cli/GenerateCommand.ts +195 -195
- package/src/cli/InitCommand.ts +107 -107
- package/src/cli/TgAccountManager.ts +50 -0
- package/src/console/migrate.ts +82 -82
- package/src/core/ApiService.ts +20 -20
- package/src/core/ApiServiceManager.ts +63 -63
- package/src/core/App.ts +1143 -1113
- package/src/core/BotArtisan.ts +79 -79
- package/src/core/BotMigration.ts +30 -30
- package/src/core/BotSeeder.ts +66 -66
- package/src/core/Model.ts +84 -84
- package/src/core/utils.ts +2 -2
- package/src/illumination/Artisan.ts +149 -149
- package/src/illumination/InlineKeyboard.ts +61 -61
- package/src/illumination/Message2Byte.ts +255 -255
- package/src/illumination/Message2ByteLiveProgressive.ts +278 -278
- package/src/illumination/Message2bytePool.ts +107 -107
- package/src/illumination/Migration.ts +186 -186
- package/src/illumination/RunSectionRoute.ts +85 -85
- package/src/illumination/Section.ts +410 -410
- package/src/illumination/SectionComponent.ts +64 -64
- package/src/illumination/Telegraf2byteContext.ts +32 -32
- package/src/index.ts +42 -35
- package/src/libs/TelegramAccountControl.ts +1140 -738
- package/src/libs/TgSender.ts +53 -0
- package/src/models/Model.ts +67 -0
- package/src/models/Proxy.ts +218 -0
- package/src/models/TgAccount.ts +362 -0
- package/src/models/index.ts +3 -0
- package/src/types.ts +191 -188
- package/src/user/UserModel.ts +297 -297
- package/src/user/UserStore.ts +119 -119
- package/src/workflow/services/MassSendApiService.ts +80 -80
- package/templates/bot/.env.example +23 -19
- package/templates/bot/artisan.ts +8 -8
- package/templates/bot/bot.ts +82 -79
- package/templates/bot/database/dbConnector.ts +4 -4
- package/templates/bot/database/migrate.ts +9 -9
- package/templates/bot/database/migrations/001_create_users.sql +18 -18
- package/templates/bot/database/migrations/007_proxy.sql +27 -0
- package/templates/bot/database/migrations/008_tg_accounts.sql +32 -0
- package/templates/bot/database/seed.ts +14 -14
- package/templates/bot/docs/CLI_SERVICES.md +536 -0
- package/templates/bot/docs/INPUT_SYSTEM.md +211 -0
- package/templates/bot/docs/SERVICE_EXAMPLES.md +384 -0
- package/templates/bot/docs/TASK_SYSTEM.md +156 -0
- package/templates/bot/models/Model.ts +7 -0
- package/templates/bot/models/index.ts +2 -0
- package/templates/bot/package.json +30 -30
- package/templates/bot/sectionList.ts +9 -9
- package/templates/bot/sections/ExampleInputSection.ts +85 -85
- package/templates/bot/sections/ExampleLiveTaskerSection.ts +60 -60
- package/templates/bot/sections/HomeSection.ts +63 -63
- package/templates/bot/workflow/services/{ExampleServise.ts → ExampleService.ts} +23 -23
package/src/core/BotArtisan.ts
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { Artisan } from '../illumination/Artisan';
|
|
4
|
-
|
|
5
|
-
export interface BotArtisanOptions {
|
|
6
|
-
botName: string;
|
|
7
|
-
sectionsPath?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class BotArtisan {
|
|
11
|
-
private artisan: Artisan;
|
|
12
|
-
private options: BotArtisanOptions;
|
|
13
|
-
|
|
14
|
-
constructor(botPath: string, options: BotArtisanOptions) {
|
|
15
|
-
this.options = options;
|
|
16
|
-
this.artisan = new Artisan(botPath);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async run(): Promise<void> {
|
|
20
|
-
const command = process.argv[2];
|
|
21
|
-
const args = process.argv.slice(3);
|
|
22
|
-
|
|
23
|
-
if (!command) {
|
|
24
|
-
this.showHelp();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
switch (command) {
|
|
30
|
-
case 'make:section':
|
|
31
|
-
if (args.length === 0) {
|
|
32
|
-
console.error(chalk.red('❌ Error: Section name is required'));
|
|
33
|
-
console.log('Usage: artisan make:section SectionName');
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
await this.artisan.createSection(args[0]);
|
|
37
|
-
break;
|
|
38
|
-
|
|
39
|
-
case 'add:method':
|
|
40
|
-
if (args.length < 2) {
|
|
41
|
-
console.error(chalk.red('❌ Error: Section name and method name are required'));
|
|
42
|
-
console.log('Usage: artisan add:method SectionName methodName');
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
await this.artisan.addMethod(args[0], args[1]);
|
|
46
|
-
break;
|
|
47
|
-
|
|
48
|
-
case 'list:sections':
|
|
49
|
-
await this.artisan.listSections();
|
|
50
|
-
break;
|
|
51
|
-
|
|
52
|
-
default:
|
|
53
|
-
console.error(chalk.red(`❌ Unknown command: ${command}`));
|
|
54
|
-
this.showHelp();
|
|
55
|
-
}
|
|
56
|
-
} catch (error) {
|
|
57
|
-
if (error instanceof Error) {
|
|
58
|
-
console.error(chalk.red(`❌ Error: ${error.message}`));
|
|
59
|
-
} else {
|
|
60
|
-
console.error(chalk.red('❌ An unknown error occurred'));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private showHelp(): void {
|
|
66
|
-
console.log(`
|
|
67
|
-
🔧 ${this.options.botName} Artisan CLI
|
|
68
|
-
|
|
69
|
-
Available commands:
|
|
70
|
-
make:section <name> Create a new section
|
|
71
|
-
add:method <section> <name> Add a new method to existing section
|
|
72
|
-
list:sections List all sections
|
|
73
|
-
|
|
74
|
-
Examples:
|
|
75
|
-
artisan make:section Auth Create new AuthSection
|
|
76
|
-
artisan add:method Auth login Add login method to AuthSection
|
|
77
|
-
artisan list:sections Show all available sections
|
|
78
|
-
`);
|
|
79
|
-
}
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Artisan } from '../illumination/Artisan';
|
|
4
|
+
|
|
5
|
+
export interface BotArtisanOptions {
|
|
6
|
+
botName: string;
|
|
7
|
+
sectionsPath?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class BotArtisan {
|
|
11
|
+
private artisan: Artisan;
|
|
12
|
+
private options: BotArtisanOptions;
|
|
13
|
+
|
|
14
|
+
constructor(botPath: string, options: BotArtisanOptions) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.artisan = new Artisan(botPath);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async run(): Promise<void> {
|
|
20
|
+
const command = process.argv[2];
|
|
21
|
+
const args = process.argv.slice(3);
|
|
22
|
+
|
|
23
|
+
if (!command) {
|
|
24
|
+
this.showHelp();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
switch (command) {
|
|
30
|
+
case 'make:section':
|
|
31
|
+
if (args.length === 0) {
|
|
32
|
+
console.error(chalk.red('❌ Error: Section name is required'));
|
|
33
|
+
console.log('Usage: artisan make:section SectionName');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await this.artisan.createSection(args[0]);
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case 'add:method':
|
|
40
|
+
if (args.length < 2) {
|
|
41
|
+
console.error(chalk.red('❌ Error: Section name and method name are required'));
|
|
42
|
+
console.log('Usage: artisan add:method SectionName methodName');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
await this.artisan.addMethod(args[0], args[1]);
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'list:sections':
|
|
49
|
+
await this.artisan.listSections();
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
default:
|
|
53
|
+
console.error(chalk.red(`❌ Unknown command: ${command}`));
|
|
54
|
+
this.showHelp();
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof Error) {
|
|
58
|
+
console.error(chalk.red(`❌ Error: ${error.message}`));
|
|
59
|
+
} else {
|
|
60
|
+
console.error(chalk.red('❌ An unknown error occurred'));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private showHelp(): void {
|
|
66
|
+
console.log(`
|
|
67
|
+
🔧 ${this.options.botName} Artisan CLI
|
|
68
|
+
|
|
69
|
+
Available commands:
|
|
70
|
+
make:section <name> Create a new section
|
|
71
|
+
add:method <section> <name> Add a new method to existing section
|
|
72
|
+
list:sections List all sections
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
artisan make:section Auth Create new AuthSection
|
|
76
|
+
artisan add:method Auth login Add login method to AuthSection
|
|
77
|
+
artisan list:sections Show all available sections
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
80
|
}
|
package/src/core/BotMigration.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { setupMigrations } from '../console/migrate';
|
|
4
|
-
|
|
5
|
-
export interface BotMigrationOptions {
|
|
6
|
-
botPath: string;
|
|
7
|
-
migrationsPath: string;
|
|
8
|
-
databasePath: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class BotMigration {
|
|
12
|
-
private options: BotMigrationOptions;
|
|
13
|
-
|
|
14
|
-
constructor(options: BotMigrationOptions) {
|
|
15
|
-
this.options = options;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async run(): Promise<void> {
|
|
19
|
-
console.log(chalk.blue(`🗃️ Running migrations for bot...`));
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
await setupMigrations({
|
|
23
|
-
pathMigrations: this.options.migrationsPath,
|
|
24
|
-
pathDatabase: this.options.databasePath
|
|
25
|
-
});
|
|
26
|
-
} catch (error) {
|
|
27
|
-
console.error(chalk.red('❌ Migration failed:'), error);
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { setupMigrations } from '../console/migrate';
|
|
4
|
+
|
|
5
|
+
export interface BotMigrationOptions {
|
|
6
|
+
botPath: string;
|
|
7
|
+
migrationsPath: string;
|
|
8
|
+
databasePath: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class BotMigration {
|
|
12
|
+
private options: BotMigrationOptions;
|
|
13
|
+
|
|
14
|
+
constructor(options: BotMigrationOptions) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async run(): Promise<void> {
|
|
19
|
+
console.log(chalk.blue(`🗃️ Running migrations for bot...`));
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await setupMigrations({
|
|
23
|
+
pathMigrations: this.options.migrationsPath,
|
|
24
|
+
pathDatabase: this.options.databasePath
|
|
25
|
+
});
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(chalk.red('❌ Migration failed:'), error);
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
31
|
}
|
package/src/core/BotSeeder.ts
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { Database } from 'bun:sqlite';
|
|
4
|
-
|
|
5
|
-
export interface BotSeederOptions {
|
|
6
|
-
botPath: string;
|
|
7
|
-
databasePath: string;
|
|
8
|
-
seeders: Array<(db: Database) => Promise<void> | void>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class BotSeeder {
|
|
12
|
-
private options: BotSeederOptions;
|
|
13
|
-
|
|
14
|
-
constructor(options: BotSeederOptions) {
|
|
15
|
-
this.options = options;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async run(): Promise<void> {
|
|
19
|
-
const args = process.argv.slice(2);
|
|
20
|
-
const isCleanOnly = args.includes('--clean-only');
|
|
21
|
-
const isClear = args.includes('--clear');
|
|
22
|
-
|
|
23
|
-
console.log(chalk.blue(`🌱 Running seeders for bot...`));
|
|
24
|
-
|
|
25
|
-
const db = new Database(this.options.databasePath);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
if (isCleanOnly) {
|
|
29
|
-
console.log(chalk.yellow('🧹 Cleaning database...'));
|
|
30
|
-
await this.cleanDatabase(db);
|
|
31
|
-
console.log(chalk.green('✅ Database cleaned successfully!'));
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (isClear) {
|
|
36
|
-
console.log(chalk.yellow('🧹 Clearing and reseeding database...'));
|
|
37
|
-
await this.cleanDatabase(db);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
console.log(chalk.blue('🌱 Seeding database...'));
|
|
41
|
-
for (const seeder of this.options.seeders) {
|
|
42
|
-
await seeder(db);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.log(chalk.green('✅ Database seeded successfully!'));
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.error(chalk.red('❌ Seeding failed:'), error);
|
|
48
|
-
throw error;
|
|
49
|
-
} finally {
|
|
50
|
-
db.close();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private async cleanDatabase(db: Database): Promise<void> {
|
|
55
|
-
// Get all tables
|
|
56
|
-
const tables = db.query(`
|
|
57
|
-
SELECT name FROM sqlite_master
|
|
58
|
-
WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'migrations'
|
|
59
|
-
`).all() as Array<{ name: string }>;
|
|
60
|
-
|
|
61
|
-
// Clear all tables except migrations
|
|
62
|
-
for (const table of tables) {
|
|
63
|
-
db.query(`DELETE FROM ${table.name}`).run();
|
|
64
|
-
console.log(chalk.yellow(`🧹 Cleared table: ${table.name}`));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
|
|
5
|
+
export interface BotSeederOptions {
|
|
6
|
+
botPath: string;
|
|
7
|
+
databasePath: string;
|
|
8
|
+
seeders: Array<(db: Database) => Promise<void> | void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class BotSeeder {
|
|
12
|
+
private options: BotSeederOptions;
|
|
13
|
+
|
|
14
|
+
constructor(options: BotSeederOptions) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async run(): Promise<void> {
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const isCleanOnly = args.includes('--clean-only');
|
|
21
|
+
const isClear = args.includes('--clear');
|
|
22
|
+
|
|
23
|
+
console.log(chalk.blue(`🌱 Running seeders for bot...`));
|
|
24
|
+
|
|
25
|
+
const db = new Database(this.options.databasePath);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (isCleanOnly) {
|
|
29
|
+
console.log(chalk.yellow('🧹 Cleaning database...'));
|
|
30
|
+
await this.cleanDatabase(db);
|
|
31
|
+
console.log(chalk.green('✅ Database cleaned successfully!'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isClear) {
|
|
36
|
+
console.log(chalk.yellow('🧹 Clearing and reseeding database...'));
|
|
37
|
+
await this.cleanDatabase(db);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(chalk.blue('🌱 Seeding database...'));
|
|
41
|
+
for (const seeder of this.options.seeders) {
|
|
42
|
+
await seeder(db);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(chalk.green('✅ Database seeded successfully!'));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(chalk.red('❌ Seeding failed:'), error);
|
|
48
|
+
throw error;
|
|
49
|
+
} finally {
|
|
50
|
+
db.close();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async cleanDatabase(db: Database): Promise<void> {
|
|
55
|
+
// Get all tables
|
|
56
|
+
const tables = db.query(`
|
|
57
|
+
SELECT name FROM sqlite_master
|
|
58
|
+
WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != 'migrations'
|
|
59
|
+
`).all() as Array<{ name: string }>;
|
|
60
|
+
|
|
61
|
+
// Clear all tables except migrations
|
|
62
|
+
for (const table of tables) {
|
|
63
|
+
db.query(`DELETE FROM ${table.name}`).run();
|
|
64
|
+
console.log(chalk.yellow(`🧹 Cleared table: ${table.name}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
67
|
}
|
package/src/core/Model.ts
CHANGED
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import type { Database } from "bun:sqlite";
|
|
2
|
-
import { MakeManualPaginateButtonsParams, ModelPaginateParams, PaginateResult } from "../types";
|
|
3
|
-
import { Section } from "../illumination/Section";
|
|
4
|
-
|
|
5
|
-
declare global {
|
|
6
|
-
var db: Database;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export abstract class Model {
|
|
10
|
-
protected static db: Database;
|
|
11
|
-
protected static tableName: string;
|
|
12
|
-
|
|
13
|
-
static setDatabase(database: Database) {
|
|
14
|
-
this.db = database;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
protected static resolveDb(): Database {
|
|
18
|
-
if (globalThis.db) {
|
|
19
|
-
this.db = globalThis.db;
|
|
20
|
-
} else {
|
|
21
|
-
throw new Error("Database connection is not set.");
|
|
22
|
-
}
|
|
23
|
-
return this.db;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
protected static query(sql: string, params: any[] = []): any {
|
|
27
|
-
const stmt = this.db.prepare(sql);
|
|
28
|
-
return stmt.all(...params);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
protected static run(sql: string, params: any[] = []): { lastInsertRowid: number, changes: number } {
|
|
32
|
-
const stmt = this.db.prepare(sql);
|
|
33
|
-
return stmt.run(...params);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
protected static queryOne(sql: string, params: any[] = []): any {
|
|
37
|
-
const stmt = this.db.prepare(sql);
|
|
38
|
-
return stmt.get(...params);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
protected static execute(sql: string, params: any[] = []): void {
|
|
42
|
-
const stmt = this.db.prepare(sql);
|
|
43
|
-
stmt.run(...params);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
protected static exists(whereSql: string, whereParams: any[] = []): boolean {
|
|
47
|
-
const sql = `SELECT 1 FROM ${this.tableName} ${whereSql} LIMIT 1`;
|
|
48
|
-
const result = this.queryOne(sql, whereParams);
|
|
49
|
-
return !!result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
protected static transaction<T>(callback: () => T): T {
|
|
53
|
-
return this.db.transaction(callback)();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
public static async paginate(paginateParams: ModelPaginateParams): Promise<PaginateResult> {
|
|
57
|
-
const { page, route, routeParams, limit, whereSql, whereParams } = paginateParams;
|
|
58
|
-
const offset = (page - 1) * limit;
|
|
59
|
-
const sql = `SELECT * FROM ${this.tableName} ${whereSql} LIMIT ${offset}, ${limit}`;
|
|
60
|
-
|
|
61
|
-
const result = await this.query(sql, whereParams);
|
|
62
|
-
const queryTotal = await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} ${whereSql}`, whereParams);
|
|
63
|
-
const total = queryTotal ? queryTotal.count : 0;
|
|
64
|
-
const totalPages = Math.ceil(total / limit);
|
|
65
|
-
const hasPreviousPage = page > 1;
|
|
66
|
-
const hasNextPage = page < totalPages;
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
items: result,
|
|
70
|
-
paginateButtons: Section.makeManualPaginateButtons({
|
|
71
|
-
callbackDataAction: route,
|
|
72
|
-
paramsQuery: routeParams || {},
|
|
73
|
-
currentPage: page,
|
|
74
|
-
totalRecords: total,
|
|
75
|
-
perPage: limit,
|
|
76
|
-
} as MakeManualPaginateButtonsParams),
|
|
77
|
-
total,
|
|
78
|
-
totalPages,
|
|
79
|
-
hasPreviousPage,
|
|
80
|
-
hasNextPage,
|
|
81
|
-
currentPage: page,
|
|
82
|
-
} as PaginateResult;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
import { MakeManualPaginateButtonsParams, ModelPaginateParams, PaginateResult } from "../types";
|
|
3
|
+
import { Section } from "../illumination/Section";
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
var db: Database;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export abstract class Model {
|
|
10
|
+
protected static db: Database;
|
|
11
|
+
protected static tableName: string;
|
|
12
|
+
|
|
13
|
+
static setDatabase(database: Database) {
|
|
14
|
+
this.db = database;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected static resolveDb(): Database {
|
|
18
|
+
if (globalThis.db) {
|
|
19
|
+
this.db = globalThis.db;
|
|
20
|
+
} else {
|
|
21
|
+
throw new Error("Database connection is not set.");
|
|
22
|
+
}
|
|
23
|
+
return this.db;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected static query(sql: string, params: any[] = []): any {
|
|
27
|
+
const stmt = this.db.prepare(sql);
|
|
28
|
+
return stmt.all(...params);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected static run(sql: string, params: any[] = []): { lastInsertRowid: number, changes: number } {
|
|
32
|
+
const stmt = this.db.prepare(sql);
|
|
33
|
+
return stmt.run(...params);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected static queryOne(sql: string, params: any[] = []): any {
|
|
37
|
+
const stmt = this.db.prepare(sql);
|
|
38
|
+
return stmt.get(...params);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected static execute(sql: string, params: any[] = []): void {
|
|
42
|
+
const stmt = this.db.prepare(sql);
|
|
43
|
+
stmt.run(...params);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected static exists(whereSql: string, whereParams: any[] = []): boolean {
|
|
47
|
+
const sql = `SELECT 1 FROM ${this.tableName} ${whereSql} LIMIT 1`;
|
|
48
|
+
const result = this.queryOne(sql, whereParams);
|
|
49
|
+
return !!result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected static transaction<T>(callback: () => T): T {
|
|
53
|
+
return this.db.transaction(callback)();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static async paginate(paginateParams: ModelPaginateParams): Promise<PaginateResult> {
|
|
57
|
+
const { page, route, routeParams, limit, whereSql, whereParams } = paginateParams;
|
|
58
|
+
const offset = (page - 1) * limit;
|
|
59
|
+
const sql = `SELECT * FROM ${this.tableName} ${whereSql} LIMIT ${offset}, ${limit}`;
|
|
60
|
+
|
|
61
|
+
const result = await this.query(sql, whereParams);
|
|
62
|
+
const queryTotal = await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} ${whereSql}`, whereParams);
|
|
63
|
+
const total = queryTotal ? queryTotal.count : 0;
|
|
64
|
+
const totalPages = Math.ceil(total / limit);
|
|
65
|
+
const hasPreviousPage = page > 1;
|
|
66
|
+
const hasNextPage = page < totalPages;
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
items: result,
|
|
70
|
+
paginateButtons: Section.makeManualPaginateButtons({
|
|
71
|
+
callbackDataAction: route,
|
|
72
|
+
paramsQuery: routeParams || {},
|
|
73
|
+
currentPage: page,
|
|
74
|
+
totalRecords: total,
|
|
75
|
+
perPage: limit,
|
|
76
|
+
} as MakeManualPaginateButtonsParams),
|
|
77
|
+
total,
|
|
78
|
+
totalPages,
|
|
79
|
+
hasPreviousPage,
|
|
80
|
+
hasNextPage,
|
|
81
|
+
currentPage: page,
|
|
82
|
+
} as PaginateResult;
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/core/utils.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export function nameToCapitalize(name: string): string {
|
|
2
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
1
|
+
export function nameToCapitalize(name: string): string {
|
|
2
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
3
3
|
}
|