Skip to content

Commit

Permalink
Switch to singleton approach (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yasamato authored Dec 9, 2024
1 parent c393c41 commit c14587d
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 59 deletions.
4 changes: 2 additions & 2 deletions auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import Discord from 'next-auth/providers/discord'
import { addUser } from './lib/db/users'
import { AccountType, type User } from './types/User'
import { MongoDBAdapter } from '@auth/mongodb-adapter'
import { dbClient } from './lib/db/db'
import type { NextAuthConfig } from 'next-auth'
import { findOneTyped } from './lib/db/dbTyped'
import { Types } from './types/Components'
import clientPromise from './lib/db/mongoDB'

export const authOptions: NextAuthConfig = {
providers: [Discord],
Expand Down Expand Up @@ -85,7 +85,7 @@ export const authOptions: NextAuthConfig = {
},

// A database is optional, but required to persist accounts in a database
adapter: MongoDBAdapter(dbClient(), {
adapter: MongoDBAdapter(clientPromise, {
collections: {
Users: 'nextauth_users',
Sessions: 'nextauth_sessions',
Expand Down
40 changes: 11 additions & 29 deletions lib/db/db.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// docker run --name index-db -d -p 27017:27017 mongo
import { MongoClient } from 'mongodb'
import { hasOwnProperty } from '../utils'
import { cleanId, polluteId } from './utils'
import clientPromise from './mongoDB'

const uri =
'DATABASE_URL' in process.env
Expand All @@ -11,8 +11,6 @@ if (typeof uri !== 'string') {
throw Error('Unable to connect to DB due to missing DATABASE_URL')
}

export const dbClient = () => new MongoClient(uri, { maxPoolSize: 5 })

export async function exportData(isAdmin = false) {
if (isAdmin) {
return {
Expand All @@ -34,10 +32,8 @@ export async function exportData(isAdmin = false) {
}

export async function getAll(collection: string): Promise<object[]> {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
let data = await db.collection(collection).find().toArray()
client.close()
if (data.length > 0 && hasOwnProperty(data[0], 'name')) {
data = data.sort((a, b) => (a.name < b.name ? -1 : 1))
}
Expand All @@ -49,21 +45,18 @@ export async function find(
collection: string,
query: Record<string, any>
): Promise<object[]> {
const client = await dbClient().connect()
const db = client.db('index')
const data = await db.collection(collection).find(polluteId(query)).toArray()
client.close()
return cleanId(data)
const db = (await clientPromise).db('index')
return cleanId(
await db.collection(collection).find(polluteId(query)).toArray()
)
}

export async function findOne(
collection: string,
query: Record<string, any>
): Promise<object | null> {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const found = await db.collection(collection).findOne(polluteId(query))
client.close()
if (found === null) {
return null
}
Expand All @@ -74,10 +67,8 @@ export async function count(
collection: string,
query: Record<string, any> = {}
): Promise<number> {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const data = await db.collection(collection).countDocuments(polluteId(query))
client.close()
return data
}

Expand All @@ -92,15 +83,13 @@ export async function insert(
collection: string,
data: Record<string, any>
): Promise<string> {
const client = await dbClient().connect()
let tries = 1
while (tries < 3) {
try {
const db = client.db('index')
const db = (await clientPromise).db('index')
data.createdAt = new Date()
data.lastModified = new Date()
const { insertedId } = await db.collection(collection).insertOne(data)
client.close()
return insertedId.toString()
} catch (error) {
console.error(
Expand All @@ -112,7 +101,6 @@ export async function insert(
tries++
}
}
client.close()
throw Error('Unable to insert entry into ' + collection + ' after 3 retires')
}

Expand All @@ -121,16 +109,14 @@ export async function updateOne(
query: Record<string, any>,
data: Record<string, any>
) {
const client = await dbClient().connect()
let tries = 1
while (tries < 3) {
try {
const db = client.db('index')
const db = (await clientPromise).db('index')
await db.collection(collection).updateOne(polluteId(query), {
$set: data,
$currentDate: { lastModified: true },
})
client.close()
return
} catch (error) {
console.error(
Expand All @@ -139,21 +125,18 @@ export async function updateOne(
tries++
}
}
client.close()
throw Error('Unable to update entry of ' + collection + ' after 3 retires')
}

export async function deleteOne(
collection: string,
query: Record<string, any>
) {
const client = await dbClient().connect()
let tries = 1
while (tries < 3) {
try {
const db = client.db('index')
const db = (await clientPromise).db('index')
await db.collection(collection).deleteOne(polluteId(query))
client.close()
return
} catch (error) {
console.error(
Expand All @@ -165,6 +148,5 @@ export async function deleteOne(
tries++
}
}
client.close()
throw Error('Unable to delete entry from ' + collection + ' after 3 retires')
}
34 changes: 10 additions & 24 deletions lib/db/itemScreenshots.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dbClient } from './db'
import { GridFSBucket } from 'mongodb'
import { Readable } from 'stream'
import clientPromise from './mongoDB'

export function bufferToStream(buffer: Buffer) {
let stream = new Readable()
Expand Down Expand Up @@ -32,8 +32,7 @@ export async function addItemScreenshot(img: Uint8Array, itemId: string) {
imgStream.push(img)
imgStream.push(null)

const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const bucket = new GridFSBucket(db, {
bucketName: 'itemScreenshots',
})
Expand All @@ -57,51 +56,38 @@ export async function addItemScreenshot(img: Uint8Array, itemId: string) {
stream.on('finish', resolve)
imgStream.on('error', reject)
})
client.close()
}

export async function getItemScreenshotBuffer(itemId: string) {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const bucket = new GridFSBucket(db, {
bucketName: 'itemScreenshots',
})

const data = await streamToBuffer(bucket.openDownloadStreamByName(itemId))
client.close()
return data
return await streamToBuffer(bucket.openDownloadStreamByName(itemId))
}

export async function screenshotExists(itemId: string) {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const bucket = new GridFSBucket(db, {
bucketName: 'itemScreenshots',
})
const cursor = await bucket.find({ filename: itemId })
const data = await cursor.hasNext()
client.close()
return data
return await cursor.hasNext()
}

export async function clearAllScreenshots() {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const bucket = new GridFSBucket(db, {
bucketName: 'itemScreenshots',
})
const data = await bucket.drop()
client.close()
return data
return await bucket.drop()
}

export async function listScreenshotsOfItem(itemId: string) {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const bucket = new GridFSBucket(db, {
bucketName: 'itemScreenshots',
})
const data = bucket.find({ filename: itemId })
client.close()
return data
return bucket.find({ filename: itemId })
}
44 changes: 44 additions & 0 deletions lib/db/mongoDB.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { MongoClient } from 'mongodb'

const uri = (
'DATABASE_URL' in process.env
? process.env.DATABASE_URL
: 'mongodb://localhost'
) as string
if (typeof uri !== 'string') {
throw Error('Unable to connect to DB due to missing DATABASE_URL')
}

declare global {
var _mongoClientPromise: Promise<MongoClient>
}

class Singleton {
private static _instance: Singleton
private client: MongoClient
private clientPromise: Promise<MongoClient>
private constructor() {
this.client = new MongoClient(uri, {
maxPoolSize: 5,
directConnection: true,
})
this.clientPromise = this.client.connect()
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
global._mongoClientPromise = this.clientPromise
}
}

public static get instance() {
if (!this._instance) {
this._instance = new Singleton()
}
return this._instance.clientPromise
}
}
const clientPromise = Singleton.instance

// Export a module-scoped MongoClient promise. By doing this in a
// separate module, the client can be shared across functions.
export default clientPromise
7 changes: 3 additions & 4 deletions lib/db/views.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { count, dbClient, deleteOne, find, findOne, getAll, insert } from './db'
import { count, deleteOne, find, findOne, getAll, insert } from './db'
import { cleanId } from './utils'
import { Types } from '../../types/Components'
import { findOneTyped } from './dbTyped'
import clientPromise from './mongoDB'

export async function getViews() {
return await getAll('views')
}

export async function getLastViews(type: Types, n: number) {
const client = await dbClient().connect()
const db = client.db('index')
const db = (await clientPromise).db('index')
const data = cleanId(
await db
.collection('views')
Expand All @@ -18,7 +18,6 @@ export async function getLastViews(type: Types, n: number) {
.limit(n)
.toArray()
)
client.close()
console.log('Found', data.length, 'entries in views table for', type)

// count what has been popular recently
Expand Down

0 comments on commit c14587d

Please sign in to comment.