@2byte/tgbot-framework 1.0.2 → 1.0.4

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.
Files changed (36) hide show
  1. package/bin/2byte-cli.ts +13 -0
  2. package/package.json +6 -1
  3. package/src/cli/GenerateCommand.ts +93 -9
  4. package/src/cli/TgAccountManager.ts +50 -0
  5. package/src/core/ApiService.ts +21 -0
  6. package/src/core/ApiServiceManager.ts +63 -0
  7. package/src/core/App.ts +133 -32
  8. package/src/illumination/InlineKeyboard.ts +2 -1
  9. package/src/illumination/Message2Byte.ts +2 -1
  10. package/src/illumination/Message2ByteLiveProgressive.ts +2 -2
  11. package/src/illumination/Section.ts +1 -1
  12. package/src/index.ts +9 -0
  13. package/src/libs/TelegramAccountControl.ts +409 -7
  14. package/src/libs/TgSender.ts +53 -0
  15. package/src/models/Model.ts +67 -0
  16. package/src/models/Proxy.ts +218 -0
  17. package/src/models/TgAccount.ts +362 -0
  18. package/src/models/index.ts +3 -0
  19. package/src/types.ts +6 -1
  20. package/src/user/UserModel.ts +9 -0
  21. package/src/workflow/services/MassSendApiService.ts +80 -0
  22. package/templates/bot/.env.example +6 -1
  23. package/templates/bot/bot.ts +6 -1
  24. package/templates/bot/database/migrations/007_proxy.sql +27 -0
  25. package/templates/bot/database/migrations/008_tg_accounts.sql +32 -0
  26. package/templates/bot/docs/CLI_SERVICES.md +536 -0
  27. package/templates/bot/docs/INPUT_SYSTEM.md +211 -0
  28. package/templates/bot/docs/SERVICE_EXAMPLES.md +384 -0
  29. package/templates/bot/docs/TASK_SYSTEM.md +156 -0
  30. package/templates/bot/models/Model.ts +7 -0
  31. package/templates/bot/models/index.ts +2 -0
  32. package/templates/bot/sectionList.ts +4 -2
  33. package/templates/bot/sections/ExampleInputSection.ts +85 -0
  34. package/templates/bot/sections/ExampleLiveTaskerSection.ts +60 -0
  35. package/templates/bot/sections/HomeSection.ts +10 -10
  36. package/templates/bot/workflow/services/ExampleService.ts +24 -0
@@ -191,6 +191,7 @@ export default class Message2byte {
191
191
  }
192
192
 
193
193
  async send() {
194
+ // console.log("Sending message:", this.messageValue, ' Extra:', this.messageExtra, 'IsUpdate:', this.isUpdate);
194
195
  if (this.isUpdate) {
195
196
  if (this.section.route.runIsCallbackQuery && this.doAnswerCbQuery) {
196
197
  await this.ctx.answerCbQuery();
@@ -225,7 +226,7 @@ export default class Message2byte {
225
226
  if (typeof messageEntity === "object" && 'message_id' in messageEntity) {
226
227
  this.messageId = messageEntity.message_id as number;
227
228
  }
228
-
229
+
229
230
  return messageEntity;
230
231
  }
231
232
  }
@@ -148,7 +148,7 @@ export default class Message2ByteLiveProgressive {
148
148
  /**
149
149
  * Останавливает прогрессбар
150
150
  */
151
- stopSleepProgress(): this {
151
+ async stopSleepProgress(): Promise<this> {
152
152
  if (this.progressBarTimer) {
153
153
  clearInterval(this.progressBarTimer);
154
154
  this.progressBarTimer = undefined;
@@ -162,7 +162,7 @@ export default class Message2ByteLiveProgressive {
162
162
  }
163
163
  this.activeProgressItem = undefined;
164
164
  this.updateMessage();
165
- this.message2bytePool.send();
165
+ await this.message2bytePool.send();
166
166
  }
167
167
 
168
168
  return this;
@@ -355,7 +355,7 @@ export class Section {
355
355
  return params;
356
356
  }
357
357
 
358
- makeInlineKeyboard(buttons: any[][]): InlineKeyboard {
358
+ makeInlineKeyboard(buttons: any[][] = []): InlineKeyboard {
359
359
  const keyboard = InlineKeyboard.init(this.ctx, this);
360
360
  buttons.forEach((row) => {
361
361
  keyboard.append(row);
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  // Core classes
2
2
  export { App } from './core/App';
3
+ export { ApiService } from './core/ApiService';
4
+ export { ApiServiceManager } from './core/ApiServiceManager';
3
5
  export { BotArtisan } from './core/BotArtisan';
4
6
  export { BotMigration } from './core/BotMigration';
5
7
  export { BotSeeder } from './core/BotSeeder';
@@ -24,10 +26,17 @@ export * from './types';
24
26
  export { CreateBotCommand } from './cli/CreateBotCommand';
25
27
  export { InitCommand } from './cli/InitCommand';
26
28
  export { GenerateCommand } from './cli/GenerateCommand';
29
+ export { manualAdderTgAccount } from './cli/TgAccountManager';
30
+
31
+ // Model exports
32
+ export * from './models';
27
33
 
28
34
  // User exports
29
35
  export { UserModel } from './user/UserModel';
30
36
  export { UserStore } from './user/UserStore';
31
37
 
38
+ export * from './libs/TelegramAccountControl';
39
+ export { TgSender } from './libs/TgSender';
40
+
32
41
  // Type exports
33
42
  export * from './types';
@@ -3,11 +3,13 @@ import { StringSession } from "telegram/sessions";
3
3
  import fs from "fs";
4
4
  import { TelegramClientParams } from "telegram/client/telegramBaseClient";
5
5
  import { ProxyInterface } from "telegram/network/connection/TCPMTProxy";
6
+ import type { Database } from "bun:sqlite";
7
+ import { TgAccount, Proxy, TgAccountStatus, ProxyStatus } from '../models';
6
8
 
7
- interface TelegramRegistrarInit {
9
+ interface TelegramAccountRemoteInit {
8
10
  appId: string;
9
11
  appHash: string;
10
- credetialsManager: TelegramManagerCredentials;
12
+ credetialsManager: TelegramManagerCredentialsDriver;
11
13
  }
12
14
 
13
15
  type TelegramClientProxy = {
@@ -38,6 +40,8 @@ interface TelegramCredentialsManagerInit {
38
40
  pathFileCounterOffset: string;
39
41
  }
40
42
 
43
+ export type TelegramManagerCredentialsDriver = TelegramManagerCredentials | TelegramManagerCredentialsDB;
44
+
41
45
  export class TelegramManagerCredentials {
42
46
  private requiredProxyForCredentials: boolean = false;
43
47
  private credentials: TelegramCredentials[] = [];
@@ -219,7 +223,11 @@ export class TelegramManagerCredentials {
219
223
  * Add or update credential
220
224
  */
221
225
  addCredential(credential: TelegramCredentials): void {
222
-
226
+ // replace +
227
+ if (credential.phone.includes('+')) {
228
+ credential.phone = credential.phone.replace('+', '');
229
+ }
230
+
223
231
  // Attach proxy to credential
224
232
  if (this.requiredProxyForCredentials && !credential.proxy) {
225
233
  try {
@@ -327,15 +335,15 @@ export class TelegramManagerCredentials {
327
335
  }
328
336
 
329
337
  export class TelegramAccountRemote {
330
- private initOptions: TelegramRegistrarInit;
338
+ private initOptions: TelegramAccountRemoteInit;
331
339
  private tgClient!: TelegramClient; // Используем definite assignment assertion
332
- private credentialsManager: TelegramManagerCredentials;
340
+ private credentialsManager: TelegramManagerCredentialsDriver;
333
341
 
334
- static init(initOptions: TelegramRegistrarInit) {
342
+ static init(initOptions: TelegramAccountRemoteInit) {
335
343
  return new TelegramAccountRemote(initOptions);
336
344
  }
337
345
 
338
- constructor(initOptions: TelegramRegistrarInit) {
346
+ constructor(initOptions: TelegramAccountRemoteInit) {
339
347
  this.initOptions = initOptions;
340
348
  this.credentialsManager = initOptions.credetialsManager;
341
349
  }
@@ -736,3 +744,397 @@ export class TelegramAccountRemote {
736
744
  }
737
745
  }
738
746
  }
747
+
748
+ /**
749
+ * Менеджер учетных данных Telegram с поддержкой базы данных SQLite
750
+ */
751
+ export class TelegramManagerCredentialsDB {
752
+ private database: Database;
753
+ private requiredProxyForCredentials: boolean = false;
754
+
755
+ // Локальные импорты для избежания конфликтов путей
756
+ private TgAccount: any;
757
+ private Proxy: any;
758
+ private TgAccountStatus: any;
759
+ private ProxyStatus: any;
760
+
761
+ constructor(database: Database) {
762
+ this.database = database;
763
+
764
+ // Динамический импорт моделей
765
+ this.initModels();
766
+ }
767
+
768
+ private async initModels() {
769
+ this.TgAccount = TgAccount;
770
+ this.Proxy = Proxy;
771
+ this.TgAccountStatus = TgAccountStatus;
772
+ this.ProxyStatus = ProxyStatus;
773
+
774
+ // Устанавливаем базу данных для моделей
775
+ this.TgAccount.setDatabase(this.database);
776
+ this.Proxy.setDatabase(this.database);
777
+ }
778
+
779
+ /**
780
+ * Включить/выключить обязательное использование прокси для учетных данных
781
+ */
782
+ setRequiredProxyForCredentials(required: boolean): void {
783
+ this.requiredProxyForCredentials = required;
784
+ }
785
+
786
+ /**
787
+ * Преобразовать данные прокси из БД в формат TelegramClientProxy
788
+ */
789
+ private convertProxyToTelegramFormat(proxyData: any): TelegramClientProxy {
790
+ return {
791
+ ip: proxyData.ip,
792
+ port: proxyData.port,
793
+ username: proxyData.username || undefined,
794
+ password: proxyData.password || undefined,
795
+ secret: proxyData.secret || undefined,
796
+ socksType: (proxyData.socksType === 4 || proxyData.socksType === 5) ? proxyData.socksType as 4 | 5 : 5,
797
+ MTProxy: proxyData.MTProxy === 1
798
+ };
799
+ }
800
+
801
+ /**
802
+ * Получить следующий доступный прокси из БД
803
+ */
804
+ private async getNextProxy(): Promise<TelegramClientProxy> {
805
+ const proxy = await this.Proxy.getRandomActive();
806
+ if (!proxy) {
807
+ throw new Error("Нет доступных прокси в базе данных");
808
+ }
809
+
810
+ return this.convertProxyToTelegramFormat(proxy);
811
+ }
812
+
813
+ /**
814
+ * Добавить или обновить учетные данные
815
+ */
816
+ async addCredential(credential: TelegramCredentials): Promise<void> {
817
+ let proxyId: number | null = null;
818
+
819
+ // Назначить прокси если требуется
820
+ if (this.requiredProxyForCredentials && !credential.proxy) {
821
+ try {
822
+ const proxy = await this.getNextProxy();
823
+ credential.proxy = proxy;
824
+
825
+ // Найти ID прокси в БД
826
+ const proxyData = await this.Proxy.findByIpPort(proxy.ip, proxy.port);
827
+ if (proxyData) {
828
+ proxyId = proxyData.id!;
829
+ }
830
+ } catch (error) {
831
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
832
+ throw new Error(`Не удалось назначить прокси: ${errorMessage}`);
833
+ }
834
+ } else if (credential.proxy) {
835
+ // Найти существующий прокси или создать новый
836
+ let proxyData = await this.Proxy.findByIpPort(credential.proxy.ip, credential.proxy.port);
837
+ if (!proxyData) {
838
+ // Создать новый прокси в БД
839
+ proxyId = await this.Proxy.create({
840
+ ip: credential.proxy.ip,
841
+ port: credential.proxy.port,
842
+ username: credential.proxy.username || null,
843
+ password: credential.proxy.password || null,
844
+ secret: credential.proxy.secret || null,
845
+ socksType: credential.proxy.socksType || null,
846
+ MTProxy: credential.proxy.MTProxy ? 1 : 0,
847
+ status: this.ProxyStatus.ACTIVE
848
+ });
849
+ } else {
850
+ proxyId = proxyData.id!;
851
+ }
852
+ }
853
+
854
+ // Используем upsert для вставки или обновления
855
+ await this.TgAccount.upsert({
856
+ phone: credential.phone.replace('+', ''),
857
+ session: credential.session || null,
858
+ password: credential.password || null,
859
+ proxy_id: proxyId,
860
+ status: credential.session ? this.TgAccountStatus.ACTIVE : this.TgAccountStatus.INACTIVE
861
+ });
862
+ }
863
+
864
+ /**
865
+ * Получить учетные данные по номеру телефона
866
+ */
867
+ async getCredential(phone: string): Promise<TelegramCredentials | null> {
868
+ const accountData = await this.TgAccount.findWithProxy(phone);
869
+ if (!accountData) {
870
+ return null;
871
+ }
872
+
873
+ let proxy: TelegramClientProxy | undefined;
874
+ if (accountData.proxy_id && accountData.proxy_ip) {
875
+ proxy = {
876
+ ip: accountData.proxy_ip,
877
+ port: accountData.proxy_port,
878
+ username: accountData.proxy_username || undefined,
879
+ password: accountData.proxy_password || undefined,
880
+ secret: accountData.proxy_secret || undefined,
881
+ socksType: (accountData.proxy_socksType === 4 || accountData.proxy_socksType === 5) ?
882
+ accountData.proxy_socksType as 4 | 5 : 5,
883
+ MTProxy: accountData.proxy_MTProxy === 1
884
+ };
885
+ }
886
+
887
+ return {
888
+ phone: accountData.phone,
889
+ session: accountData.session || undefined,
890
+ password: accountData.password || undefined,
891
+ proxy
892
+ };
893
+ }
894
+
895
+ /**
896
+ * Удалить учетные данные по номеру телефона
897
+ */
898
+ async deleteCredential(phone: string): Promise<boolean> {
899
+ const account = await this.TgAccount.findByPhone(phone);
900
+ if (account) {
901
+ await this.TgAccount.delete(phone);
902
+ return true;
903
+ }
904
+ return false;
905
+ }
906
+
907
+ /**
908
+ * Получить все учетные данные
909
+ */
910
+ async getAllCredentials(): Promise<TelegramCredentials[]> {
911
+ const accounts = await this.TgAccount.getAllWithProxy();
912
+ const credentials: TelegramCredentials[] = [];
913
+
914
+ for (const account of accounts) {
915
+ let proxy: TelegramClientProxy | undefined;
916
+
917
+ if (account.proxy_id && account.proxy_ip) {
918
+ proxy = {
919
+ ip: account.proxy_ip,
920
+ port: account.proxy_port,
921
+ username: account.proxy_username || undefined,
922
+ password: account.proxy_password || undefined,
923
+ secret: account.proxy_secret || undefined,
924
+ socksType: (account.proxy_socksType === 4 || account.proxy_socksType === 5) ?
925
+ account.proxy_socksType as 4 | 5 : 5,
926
+ MTProxy: account.proxy_MTProxy === 1
927
+ };
928
+ }
929
+
930
+ credentials.push({
931
+ phone: account.phone,
932
+ session: account.session || undefined,
933
+ password: account.password || undefined,
934
+ proxy
935
+ });
936
+ }
937
+
938
+ return credentials;
939
+ }
940
+
941
+ /**
942
+ * Проверить, существуют ли учетные данные
943
+ */
944
+ async hasCredential(phone: string): Promise<boolean> {
945
+ return await this.TgAccount.exists(phone);
946
+ }
947
+
948
+ /**
949
+ * Обновить сессию для существующего аккаунта
950
+ */
951
+ async updateSession(phone: string, session: string): Promise<boolean> {
952
+ const account = await this.TgAccount.findByPhone(phone);
953
+ if (account) {
954
+ await this.TgAccount.updateSession(phone, session);
955
+ await this.TgAccount.updateStatus(phone, this.TgAccountStatus.ACTIVE);
956
+ return true;
957
+ }
958
+ return false;
959
+ }
960
+
961
+ /**
962
+ * Получить количество учетных данных
963
+ */
964
+ async getCredentialsCount(): Promise<number> {
965
+ const stats = await this.TgAccount.getStats();
966
+ return stats.total;
967
+ }
968
+
969
+ /**
970
+ * Очистить все учетные данные
971
+ */
972
+ async clearAllCredentials(): Promise<void> {
973
+ const accounts = await this.TgAccount.getAll();
974
+ for (const account of accounts) {
975
+ await this.TgAccount.delete(account.id!);
976
+ }
977
+ }
978
+
979
+ /**
980
+ * Получить учетные данные по прокси
981
+ */
982
+ async getCredentialsByProxy(proxyHost: string, proxyPort: number): Promise<TelegramCredentials[]> {
983
+ const proxy = await this.Proxy.findByIpPort(proxyHost, proxyPort);
984
+ if (!proxy) {
985
+ return [];
986
+ }
987
+
988
+ const accounts = await this.TgAccount.getAll();
989
+ const credentials: TelegramCredentials[] = [];
990
+
991
+ for (const account of accounts) {
992
+ if (account.proxy_id === proxy.id) {
993
+ const proxyData = this.convertProxyToTelegramFormat(proxy);
994
+ credentials.push({
995
+ phone: account.phone,
996
+ session: account.session || undefined,
997
+ password: account.password || undefined,
998
+ proxy: proxyData
999
+ });
1000
+ }
1001
+ }
1002
+
1003
+ return credentials;
1004
+ }
1005
+
1006
+ /**
1007
+ * Получить учетные данные без прокси
1008
+ */
1009
+ async getCredentialsWithoutProxy(): Promise<TelegramCredentials[]> {
1010
+ const accounts = await this.TgAccount.getWithoutProxy();
1011
+
1012
+ return accounts.map(account => ({
1013
+ phone: account.phone,
1014
+ session: account.session || undefined,
1015
+ password: account.password || undefined
1016
+ }));
1017
+ }
1018
+
1019
+ /**
1020
+ * Получить активные учетные данные
1021
+ */
1022
+ async getActiveCredentials(): Promise<TelegramCredentials[]> {
1023
+ const accounts = await this.TgAccount.getByStatus(this.TgAccountStatus.ACTIVE);
1024
+ const credentials: TelegramCredentials[] = [];
1025
+
1026
+ for (const account of accounts) {
1027
+ let proxy: TelegramClientProxy | undefined;
1028
+
1029
+ if (account.proxy_id) {
1030
+ const proxyData = await this.Proxy.findById(account.proxy_id);
1031
+ if (proxyData && proxyData.status === this.ProxyStatus.ACTIVE) {
1032
+ proxy = this.convertProxyToTelegramFormat(proxyData);
1033
+ }
1034
+ }
1035
+
1036
+ credentials.push({
1037
+ phone: account.phone,
1038
+ session: account.session || undefined,
1039
+ password: account.password || undefined,
1040
+ proxy
1041
+ });
1042
+ }
1043
+
1044
+ return credentials;
1045
+ }
1046
+
1047
+ /**
1048
+ * Получить случайные активные учетные данные
1049
+ */
1050
+ async getRandomActiveCredential(): Promise<TelegramCredentials | null> {
1051
+ const account = await this.TgAccount.getRandomAvailable();
1052
+ if (!account) {
1053
+ return null;
1054
+ }
1055
+
1056
+ let proxy: TelegramClientProxy | undefined;
1057
+ if (account.proxy_id) {
1058
+ const proxyData = await this.Proxy.findById(account.proxy_id);
1059
+ if (proxyData && proxyData.status === this.ProxyStatus.ACTIVE) {
1060
+ proxy = this.convertProxyToTelegramFormat(proxyData);
1061
+ }
1062
+ }
1063
+
1064
+ return {
1065
+ phone: account.phone,
1066
+ session: account.session || undefined,
1067
+ password: account.password || undefined,
1068
+ proxy
1069
+ };
1070
+ }
1071
+
1072
+ /**
1073
+ * Обновить информацию о пользователе
1074
+ */
1075
+ async updateUserInfo(phone: string, userInfo: {
1076
+ first_name?: string;
1077
+ last_name?: string;
1078
+ username?: string;
1079
+ user_id?: number;
1080
+ }): Promise<void> {
1081
+ await this.TgAccount.updateUserInfo(phone, userInfo);
1082
+ }
1083
+
1084
+ /**
1085
+ * Обновить статус аккаунта
1086
+ */
1087
+ async updateAccountStatus(phone: string, status: any): Promise<void> {
1088
+ const account = await this.TgAccount.findByPhone(phone);
1089
+ if (account) {
1090
+ await this.TgAccount.updateStatus(account.id!, status);
1091
+ }
1092
+ }
1093
+
1094
+ /**
1095
+ * Отметить активность аккаунта
1096
+ */
1097
+ async markActivity(phone: string): Promise<void> {
1098
+ const account = await this.TgAccount.findByPhone(phone);
1099
+ if (account) {
1100
+ await this.TgAccount.updateLastActivity(account.id!);
1101
+ }
1102
+ }
1103
+
1104
+ /**
1105
+ * Получить статистику
1106
+ */
1107
+ async getStats(): Promise<{
1108
+ accounts: any;
1109
+ proxies: any;
1110
+ }> {
1111
+ const accountStats = await this.TgAccount.getStats();
1112
+ const proxyStats = await this.Proxy.getStats();
1113
+
1114
+ return {
1115
+ accounts: accountStats,
1116
+ proxies: proxyStats
1117
+ };
1118
+ }
1119
+
1120
+ /**
1121
+ * Добавить прокси в БД
1122
+ */
1123
+ async addProxy(proxyData: any): Promise<number> {
1124
+ return await this.Proxy.create(proxyData);
1125
+ }
1126
+
1127
+ /**
1128
+ * Получить все прокси
1129
+ */
1130
+ async getAllProxies(): Promise<any[]> {
1131
+ return await this.Proxy.getAll();
1132
+ }
1133
+
1134
+ /**
1135
+ * Получить активные прокси
1136
+ */
1137
+ async getActiveProxies(): Promise<any[]> {
1138
+ return await this.Proxy.getActive();
1139
+ }
1140
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ TelegramAccountRemote,
3
+ TelegramManagerCredentials,
4
+ TelegramCredentials,
5
+ } from "./TelegramAccountControl";
6
+
7
+ interface TgSenderParams {
8
+ tgAppId: string;
9
+ tgAppHash: string;
10
+ app: any;
11
+ }
12
+
13
+ export class TgSender {
14
+ private makeInstanceTgRemoteControl: () => {
15
+ remoteControl: TelegramAccountRemote;
16
+ credentialsManager: TelegramManagerCredentials;
17
+ };
18
+
19
+ static init(params: TgSenderParams) {
20
+ return new TgSender(params);
21
+ }
22
+
23
+ constructor(private params: TgSenderParams) {
24
+ this.makeInstanceTgRemoteControl = () => {
25
+ const credentialsManager = TelegramManagerCredentials.init({
26
+ pathFileStorage: path.join(__dirname, "../storage/tg_credentials.json"),
27
+ pathFileProxyList: path.join(__dirname, "../storage/tg_proxy_list.txt"),
28
+ pathFileCounterOffset: path.join(__dirname, "../storage/tg_counter_offset.txt"),
29
+ });
30
+
31
+ const remoteControl = TelegramAccountRemote.init({
32
+ appId: params.tgAppId!,
33
+ appHash: params.tgAppHash!,
34
+ credetialsManager: credentialsManager,
35
+ });
36
+
37
+ return {
38
+ remoteControl,
39
+ credentialsManager,
40
+ };
41
+ };
42
+ }
43
+
44
+ fromRandomAccount() {
45
+ return this;
46
+ }
47
+
48
+ fromAccountCredentials(credentials: TelegramCredentials) {
49
+ return this;
50
+ }
51
+
52
+ sendMessageByPhone(phone: string, message: string) {}
53
+ }
@@ -0,0 +1,67 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import { MakeManualPaginateButtonsParams, ModelPaginateParams, PaginateResult } from "..";
3
+ import { Section } from "..";
4
+
5
+ export abstract class Model {
6
+ protected static db: Database;
7
+ protected static tableName: string;
8
+
9
+ static setDatabase(database: Database) {
10
+ this.db = database;
11
+ }
12
+
13
+ protected static query(sql: string, params: any[] = []): any {
14
+ const stmt = this.db.prepare(sql);
15
+ return stmt.all(...params);
16
+ }
17
+
18
+ protected static queryOne(sql: string, params: any[] = []): any {
19
+ const stmt = this.db.prepare(sql);
20
+ return stmt.get(...params);
21
+ }
22
+
23
+ protected static execute(sql: string, params: any[] = []): void {
24
+ const stmt = this.db.prepare(sql);
25
+ stmt.run(...params);
26
+ }
27
+
28
+ protected static transaction<T>(callback: () => T): T {
29
+ return this.db.transaction(callback)();
30
+ }
31
+
32
+ public static async paginate(paginateParams: ModelPaginateParams): Promise<PaginateResult> {
33
+ const { page, route, routeParams, limit, whereSql, whereParams } = paginateParams;
34
+ const offset = (page - 1) * limit;
35
+ const sql = `SELECT * FROM ${this.tableName} ${whereSql} LIMIT ${offset}, ${limit}`;
36
+
37
+ const result = await this.query(sql, whereParams);
38
+ const queryTotal = await this.queryOne(`SELECT COUNT(*) as count FROM ${this.tableName} ${whereSql}`, whereParams);
39
+ const total = queryTotal ? queryTotal.count : 0;
40
+ const totalPages = Math.ceil(total / limit);
41
+ const hasPreviousPage = page > 1;
42
+ const hasNextPage = page < totalPages;
43
+
44
+ return {
45
+ items: result,
46
+ paginateButtons: Section.makeManualPaginateButtons({
47
+ callbackDataAction: route,
48
+ paramsQuery: routeParams || {},
49
+ currentPage: page,
50
+ totalRecords: total,
51
+ perPage: limit,
52
+ } as MakeManualPaginateButtonsParams),
53
+ total,
54
+ totalPages,
55
+ hasPreviousPage,
56
+ hasNextPage,
57
+ currentPage: page,
58
+ } as PaginateResult;
59
+ }
60
+
61
+ static getConnection(): Database {
62
+ if (db) {
63
+ return db;
64
+ }
65
+ throw new Error("Database connection is not set.");
66
+ }
67
+ }