Skip to content

Commit 63dfb92

Browse files
Merge pull request #203 from TogetherCrew/201-extend-mongolib
feat: add connetion support for repos
2 parents cd326f2 + 0db838e commit 63dfb92

20 files changed

Lines changed: 201 additions & 179 deletions

.eslintrc.json

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,19 @@
33
"es2021": true,
44
"node": true
55
},
6-
"extends": [
7-
"standard-with-typescript",
8-
"prettier"
9-
],
6+
"extends": ["standard-with-typescript", "prettier"],
107
"overrides": [],
118
"parser": "@typescript-eslint/parser",
129
"parserOptions": {
1310
"ecmaVersion": "latest",
1411
"sourceType": "module",
1512
"project": "./tsconfig.json"
1613
},
17-
"rules": {},
18-
"ignorePatterns": [
19-
"coverage",
20-
"dist",
21-
"__tests__/",
22-
"jest.config.ts",
23-
"*.yml"
24-
]
25-
}
14+
"rules": {
15+
"@typescript-eslint/interface-name-prefix": "off",
16+
"@typescript-eslint/explicit-function-return-type": "off",
17+
"@typescript-eslint/explicit-module-boundary-types": "off",
18+
"@typescript-eslint/no-explicit-any": "off"
19+
},
20+
"ignorePatterns": ["coverage", "dist", "__tests__/", "jest.config.ts", "*.yml"]
21+
}

CHANGELOG.md

Lines changed: 0 additions & 6 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"test": "jest --detectOpenHandles",
1212
"format": "prettier --write \"src/**/*.ts\" \"__tests__/**/*.ts\" \"*.ts\" ",
1313
"prepublishOnly": "npm test",
14-
"version": "npm run format && git add -A src"
14+
"version": "npm run format && git add -A src",
15+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
1516
},
1617
"repository": {
1718
"type": "git",

src/connection.ts

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,66 @@
1-
import mongoose, { Connection } from 'mongoose';
1+
import mongoose, { type Connection } from 'mongoose';
22

33
export default class MongoConnectionManager {
4-
private static instance: MongoConnectionManager;
4+
private static instance: MongoConnectionManager | undefined;
5+
56
private mongoConnection: Connection | null = null;
7+
private currentUri = '';
68

7-
// Private constructorto prevent direct instantiation
89
private constructor() {}
910

1011
public static getInstance(): MongoConnectionManager {
11-
if (typeof MongoConnectionManager.instance === 'undefined') {
12+
if (MongoConnectionManager.instance === undefined) {
1213
MongoConnectionManager.instance = new MongoConnectionManager();
1314
}
1415
return MongoConnectionManager.instance;
1516
}
1617

17-
public async connect(url: string): Promise<void> {
18+
public async connect(uri: string): Promise<void> {
19+
if (this.mongoConnection !== null && this.mongoConnection.readyState === 1) {
20+
if (uri === this.currentUri) return;
21+
throw new Error('MongoDB connection already exists with a different URI');
22+
}
23+
1824
try {
19-
if (this.mongoConnection !== null) {
20-
throw new Error('MongoDB connection already exists');
21-
}
22-
await mongoose.connect(url);
25+
await mongoose.connect(uri);
2326
this.mongoConnection = mongoose.connection;
24-
console.log('Connected to MongoDB!');
25-
} catch (error: unknown) {
26-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
27-
console.error(`Failed to connect to MongoDB: ${errorMessage}`);
28-
throw new Error(`Failed to connect to MongoDB: ${errorMessage}`);
27+
this.currentUri = uri;
28+
29+
console.log('Connected to MongoDB');
30+
} catch (err: unknown) {
31+
const msg = err instanceof Error ? err.message : 'Unknown error';
32+
console.error(`Failed to connect to MongoDB: ${msg}`);
33+
throw new Error(`Failed to connect to MongoDB: ${msg}`);
2934
}
3035
}
3136

3237
public async disconnect(): Promise<void> {
38+
if (this.mongoConnection === null) {
39+
console.warn('No active MongoDB connection to disconnect.');
40+
return;
41+
}
42+
3343
try {
34-
if (this.mongoConnection !== null) {
35-
await mongoose.disconnect();
36-
this.mongoConnection = null;
37-
console.log('Disconnected from MongoDB');
38-
} else {
39-
console.warn('No active MongoDB connection to disconnect.');
40-
}
41-
} catch (error: unknown) {
42-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
43-
console.error(`Failed to disconnect from MongoDB: ${errorMessage}`);
44-
throw new Error(`Failed to disconnect from MongoDB: ${errorMessage}`);
44+
await mongoose.disconnect();
45+
this.mongoConnection = null;
46+
this.currentUri = '';
47+
console.log('Disconnected from MongoDB');
48+
} catch (err: unknown) {
49+
const msg = err instanceof Error ? err.message : 'Unknown error';
50+
console.error(`Failed to disconnect from MongoDB: ${msg}`);
51+
throw new Error(`Failed to disconnect from MongoDB: ${msg}`);
4552
}
4653
}
4754

4855
public ensureConnected(): void {
4956
if (this.mongoConnection === null) {
50-
throw new Error('No active MongoDB connection. Please connect before performing database operations.');
57+
throw new Error('No active MongoDB connection. Please connect first.');
5158
}
5259
}
5360

54-
public getConnection(): Connection | null {
61+
public getConnection(): Connection {
5562
this.ensureConnected();
56-
return this.mongoConnection;
63+
// Non‑null assertion is safe after explicit check above.
64+
return this.mongoConnection as Connection;
5765
}
5866
}

src/databaseManager.ts

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import { Snowflake } from 'discord.js';
2-
import mongoose, { Connection } from 'mongoose';
1+
/* eslint-disable no-console */
2+
import { type Snowflake } from 'discord.js';
3+
import mongoose, { type Connection } from 'mongoose';
34

4-
import { IChannel, IGuildMember, IHeatMap, IMemberActivity, IRawInfo, IRole } from './interfaces';
5+
import {
6+
type IChannel,
7+
type IGuildMember,
8+
type IHeatMap,
9+
type IMemberActivity,
10+
type IRawInfo,
11+
type IRole,
12+
} from './interfaces';
513
import {
614
channelSchema,
715
guildMemberSchema,
@@ -12,55 +20,62 @@ import {
1220
} from './models/schemas';
1321

1422
export default class DatabaseManager {
15-
private static instance: DatabaseManager;
16-
private modelCache: Record<string, boolean> = {};
23+
private static instance: DatabaseManager | undefined;
24+
25+
private readonly modelCache = new Map<string, Promise<void>>();
1726

1827
public static getInstance(): DatabaseManager {
19-
if (typeof DatabaseManager.instance === 'undefined') {
28+
if (DatabaseManager.instance === undefined) {
2029
DatabaseManager.instance = new DatabaseManager();
2130
}
2231
return DatabaseManager.instance;
2332
}
2433

2534
public async getGuildDb(guildId: Snowflake): Promise<Connection> {
26-
const dbName = guildId;
27-
const db = mongoose.connection.useDb(dbName, { useCache: true });
35+
const db = mongoose.connection.useDb(guildId, { useCache: true });
2836
await this.setupModels(db, 'guild');
2937
return db;
3038
}
3139

3240
public async getPlatformDb(platformId: string): Promise<Connection> {
33-
const dbName = platformId;
34-
const db = mongoose.connection.useDb(dbName, { useCache: true });
41+
const db = mongoose.connection.useDb(platformId, { useCache: true });
3542
await this.setupModels(db, 'platform');
3643
return db;
3744
}
3845

3946
private async setupModels(db: Connection, dbType: 'guild' | 'platform'): Promise<void> {
40-
if (!this.modelCache[db.name]) {
41-
try {
42-
if (dbType === 'platform') {
43-
db.model<IHeatMap>('HeatMap', heatMapSchema);
44-
db.model<IMemberActivity>('MemberActivity', MemberActivitySchema);
45-
} else if (dbType === 'guild') {
46-
db.model<IRawInfo>('RawInfo', rawInfoSchema);
47-
db.model<IGuildMember>('GuildMember', guildMemberSchema);
48-
db.model<IChannel>('Channel', channelSchema);
49-
db.model<IRole>('Role', roleSchema);
47+
let compilePromise: Promise<void> | undefined = this.modelCache.get(db.name);
48+
49+
if (compilePromise === undefined) {
50+
compilePromise = (async (): Promise<void> => {
51+
try {
52+
if (dbType === 'platform') {
53+
db.model<IHeatMap>('HeatMap', heatMapSchema);
54+
db.model<IMemberActivity>('MemberActivity', MemberActivitySchema);
55+
} else {
56+
db.model<IRawInfo>('RawInfo', rawInfoSchema);
57+
db.model<IGuildMember>('GuildMember', guildMemberSchema);
58+
db.model<IChannel>('Channel', channelSchema);
59+
db.model<IRole>('Role', roleSchema);
60+
}
61+
} catch (err) {
62+
console.error(`Error setting up models for ${db.name}:`, err);
5063
}
51-
this.modelCache[db.name] = true;
52-
} catch (error) {
53-
console.error(`Error setting up models for ${db.name}:`, error);
54-
}
64+
})();
65+
66+
this.modelCache.set(db.name, compilePromise);
5567
}
68+
69+
await compilePromise;
5670
}
5771

5872
public async deleteDatabase(db: Connection): Promise<void> {
59-
const dbName = db.name;
6073
try {
6174
await db.dropDatabase();
62-
} catch (error) {
63-
console.error(`Error deleting database ${dbName}:`, error);
75+
await db.close();
76+
this.modelCache.delete(db.name);
77+
} catch (err) {
78+
console.error(`Error deleting database ${db.name}:`, err);
6479
}
6580
}
6681
}

src/interfaces/Module.interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Model, Types } from 'mongoose';
1+
import { type Model, type Types } from 'mongoose';
22

3-
import { ModuleNames, PlatformNames } from '../config/enums';
3+
import { type ModuleNames, type PlatformNames } from '../config/enums';
44

55
export interface IModule {
66
name: ModuleNames;
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Model } from 'mongoose';
1+
import { type Model } from 'mongoose';
22
import { BaseRepository } from './base.repository';
3-
import { IAnnouncement } from '../interfaces';
3+
import { type IAnnouncement } from '../interfaces';
44
import Announcement from '../models/Announcement.model';
55

66
export class AnnouncementRepository extends BaseRepository<IAnnouncement> {
@@ -10,4 +10,3 @@ export class AnnouncementRepository extends BaseRepository<IAnnouncement> {
1010
}
1111

1212
export const announcementRepository = new AnnouncementRepository();
13-
export default announcementRepository;
Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
// src/db/repositories/base.repository.ts
2-
import { FilterQuery, LeanDocument, Model, PopulateOptions, ProjectionType, QueryOptions, UpdateQuery } from 'mongoose';
1+
import {
2+
type FilterQuery,
3+
type HydratedDocument,
4+
type LeanDocument,
5+
type Model,
6+
type PopulateOptions,
7+
type ProjectionType,
8+
type QueryOptions,
9+
type UpdateQuery,
10+
} from 'mongoose';
311

412
export interface PaginateOptions {
513
page?: number;
@@ -8,51 +16,63 @@ export interface PaginateOptions {
816
populate?: string | PopulateOptions | Array<string | PopulateOptions>;
917
}
1018

19+
interface PaginateResult<T> {
20+
results: Array<LeanDocument<T>>;
21+
page: number;
22+
limit: number;
23+
totalPages: number;
24+
totalResults: number;
25+
}
26+
27+
interface PaginateModel<T> extends Model<T> {
28+
paginate: (filter: FilterQuery<T>, options: PaginateOptions) => Promise<PaginateResult<T>>;
29+
}
30+
1131
export class BaseRepository<T> {
1232
constructor(private readonly model: Model<T>) {}
1333

14-
async create(doc: Partial<T>): Promise<T> {
34+
async create(doc: Partial<T>): Promise<HydratedDocument<T>> {
1535
return await this.model.create(doc);
1636
}
1737

18-
async createMany(docs: Array<Partial<T>>): Promise<T[]> {
38+
async createMany(docs: Array<Partial<T>>): Promise<Array<HydratedDocument<T>>> {
1939
return await this.model.insertMany(docs);
2040
}
2141

22-
async findById(id: string, projection?: ProjectionType<T>, options?: QueryOptions): Promise<T | null> {
23-
return await this.model.findById(id, projection, options);
42+
async findById(id: string, projection?: ProjectionType<T>, options?: QueryOptions): Promise<LeanDocument<T> | null> {
43+
return await this.model.findById(id, projection, options).lean();
2444
}
2545

26-
async find(filter: FilterQuery<T>, projection?: ProjectionType<T>, options?: QueryOptions): Promise<T[]> {
27-
return await this.model.find(filter, projection, options);
46+
async find(
47+
filter: FilterQuery<T>,
48+
projection?: ProjectionType<T>,
49+
options?: QueryOptions,
50+
): Promise<Array<LeanDocument<T>>> {
51+
return await this.model.find(filter, projection, options).lean();
2852
}
2953

30-
async updateOne(
54+
async findOne(
3155
filter: FilterQuery<T>,
32-
update: UpdateQuery<T>,
56+
projection?: ProjectionType<T>,
3357
options?: QueryOptions,
34-
): Promise<{ acknowledged: boolean; modifiedCount: number; upsertedId: unknown }> {
58+
): Promise<LeanDocument<T> | null> {
59+
return await this.model.findOne(filter, projection, options).lean();
60+
}
61+
62+
async updateOne(filter: FilterQuery<T>, update: UpdateQuery<T>, options?: QueryOptions): Promise<any> {
3563
return await this.model.updateOne(filter, update, options);
3664
}
3765

38-
async deleteOne(filter: FilterQuery<T>): Promise<{ deletedCount?: number }> {
66+
async deleteOne(filter: FilterQuery<T>): Promise<any> {
3967
return await this.model.deleteOne(filter);
4068
}
4169

42-
async deleteMany(filter: FilterQuery<T>): Promise<{ deletedCount?: number }> {
70+
async deleteMany(filter: FilterQuery<T>): Promise<any> {
4371
return await this.model.deleteMany(filter);
4472
}
4573

46-
async paginate(
47-
filter: FilterQuery<T>,
48-
options: PaginateOptions,
49-
): Promise<{
50-
results: Array<LeanDocument<T>>;
51-
page: number;
52-
limit: number;
53-
totalPages: number;
54-
totalResults: number;
55-
}> {
56-
return (this.model as any).paginate(filter, options);
74+
async paginate(filter: FilterQuery<T>, options: PaginateOptions): Promise<PaginateResult<T>> {
75+
const modelWithPaginate = this.model as unknown as PaginateModel<T>;
76+
return await modelWithPaginate.paginate(filter, options);
5777
}
5878
}
Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { Model } from 'mongoose';
1+
import { type Connection } from 'mongoose';
2+
3+
import { type IChannel } from '../interfaces';
24
import { BaseRepository } from './base.repository';
3-
import { IChannel } from '../interfaces';
4-
import Channel from '../models/Channel.model';
55

66
export class ChannelRepository extends BaseRepository<IChannel> {
7-
constructor(model: Model<IChannel> = Channel) {
8-
super(model);
7+
constructor(connection: Connection) {
8+
super(connection.model<IChannel>('Channel'));
99
}
1010
}
1111

12-
export const channelRepository = new ChannelRepository();
13-
export default channelRepository;
12+
export const makeChannelRepository = (connection: Connection) => new ChannelRepository(connection);

0 commit comments

Comments
 (0)