Files
adam-acs/database/base_model.ts
T
Kevin L'Hereec 991173aa05 feat(auth): add session login page, API key guard, and remove orphan SQL migrations
- Add session-based authentication for web-UI (Login.vue, AuthController)
- Add AuthSessionMiddleware: redirects unauthenticated users to /login
- Add ApiKeyMiddleware: validates X-Api-Key header on all /api/v1 routes
- Add BasicAuthMiddleware: registered for future M2M use
- Use timingSafeEqual for all credential comparisons (timing attack prevention)
- Regenerate session ID on login (session fixation prevention)
- Validate redirect target after login (open redirect prevention)
- Require WEBUI_USERNAME, WEBUI_PASSWORD, API_KEY as mandatory env vars
- Share auth.username/authenticated via Inertia shared props
- Add user dropdown in navbar (avatar icon + username + logout)
- Add login i18n keys in fr.json and en.json
- Remove orphan SQL migrations (users, auth_access_tokens) and @adonisjs/lucid
2026-07-03 13:59:05 +02:00

147 lines
5.2 KiB
TypeScript

import {
type Db,
type Collection,
ObjectId,
type Filter,
type UpdateFilter,
type Sort,
} from 'mongodb'
import { getMongoDb } from '#providers/mongodb_provider'
export abstract class BaseModel {
static collectionName: string
_id?: ObjectId
createdAt?: Date
updatedAt?: Date
static getDb(): Db {
return getMongoDb()
}
static getCollection(this: { collectionName: string; getDb(): Db }): Collection {
return this.getDb().collection(this.collectionName)
}
static async find(
this: { collectionName: string; getDb(): Db },
filter: Filter<any> = {}
): Promise<any[]> {
const collection = this.getDb().collection(this.collectionName)
return collection.find(filter).toArray()
}
static async findById(this: { collectionName: string; getDb(): Db }, id: string | ObjectId): Promise<any | null> {
const collection = this.getDb().collection(this.collectionName)
const objectId = typeof id === 'string' ? new ObjectId(id) : id
return collection.findOne({ _id: objectId })
}
static async findOne(this: { collectionName: string; getDb(): Db }, filter: Filter<any>): Promise<any | null> {
const collection = this.getDb().collection(this.collectionName)
return collection.findOne(filter)
}
static async create(this: { collectionName: string; getDb(): Db }, data: Partial<any>): Promise<any> {
const collection = this.getDb().collection(this.collectionName)
const now = new Date()
const doc = { ...data, createdAt: now, updatedAt: now }
const result = await collection.insertOne(doc)
return { ...doc, _id: result.insertedId }
}
static async createMany(this: { collectionName: string; getDb(): Db }, data: Partial<any>[]): Promise<any[]> {
const collection = this.getDb().collection(this.collectionName)
const now = new Date()
const docs = data.map((d) => ({ ...d, createdAt: now, updatedAt: now }))
const result = await collection.insertMany(docs)
return docs.map((doc, i) => ({ ...doc, _id: result.insertedIds[i] }))
}
static async updateOne(
this: { collectionName: string; getDb(): Db },
filter: Filter<any>,
update: UpdateFilter<any> | Partial<any>
): Promise<any> {
const collection = this.getDb().collection(this.collectionName)
const updateDoc = '$set' in update ? update : { $set: { ...update, updatedAt: new Date() } }
return collection.updateOne(filter, updateDoc)
}
static async updateMany(
this: { collectionName: string; getDb(): Db },
filter: Filter<any>,
update: UpdateFilter<any> | Partial<any>
): Promise<any> {
const collection = this.getDb().collection(this.collectionName)
const updateDoc = '$set' in update ? update : { $set: { ...update, updatedAt: new Date() } }
return collection.updateMany(filter, updateDoc)
}
static async deleteOne(this: { collectionName: string; getDb(): Db }, filter: Filter<any>): Promise<boolean> {
const collection = this.getDb().collection(this.collectionName)
const result = await collection.deleteOne(filter)
return result.deletedCount === 1
}
static async deleteMany(this: { collectionName: string; getDb(): Db }, filter: Filter<any>): Promise<number> {
const collection = this.getDb().collection(this.collectionName)
const result = await collection.deleteMany(filter)
return result.deletedCount
}
static async paginate(
this: { collectionName: string; getDb(): Db },
page: number,
perPage: number,
filter: Filter<any> = {},
sort: Sort = {}
): Promise<{ data: any[]; total: number; page: number; perPage: number; lastPage: number }> {
const collection = this.getDb().collection(this.collectionName)
const total = await collection.countDocuments(filter)
const lastPage = Math.max(1, Math.ceil(total / perPage))
const skip = (page - 1) * perPage
const data = await collection.find(filter).sort(sort).skip(skip).limit(perPage).toArray()
return { data, total, page, perPage, lastPage }
}
static query(this: { collectionName: string; getDb(): Db }) {
const collection = this.getDb().collection(this.collectionName)
return collection
}
async save(): Promise<void> {
const constructor = this.constructor as typeof BaseModel
const collection = constructor.getDb().collection(constructor.collectionName)
this.updatedAt = new Date()
if (!this.createdAt) {
this.createdAt = new Date()
}
const { _id, ...data } = this as any
if (_id) {
await collection.updateOne({ _id }, { $set: data })
} else {
const result = await collection.insertOne(data)
this._id = result.insertedId
}
}
async delete(): Promise<boolean> {
const constructor = this.constructor as typeof BaseModel
const collection = constructor.getDb().collection(constructor.collectionName)
if (!this._id) return false
const result = await collection.deleteOne({ _id: this._id })
return result.deletedCount === 1
}
async refresh(): Promise<void> {
const constructor = this.constructor as typeof BaseModel
const collection = constructor.getDb().collection(constructor.collectionName)
if (!this._id) return
const doc = await collection.findOne({ _id: this._id })
if (doc) {
Object.assign(this, doc)
}
}
}