feat: Add Dao

This commit is contained in:
2023-06-19 00:50:46 +02:00
parent 59db3976fe
commit 5c1ed32cb6
30 changed files with 7344 additions and 6753 deletions

View File

@ -0,0 +1,21 @@
import type APIKey from '.'
import Dao from '../Dao'
export default class APIKeyDao extends Dao<APIKey> {
private idx = 0
public async create(obj: Omit<APIKey, 'id'>): Promise<APIKey | null> {
console.log('pouet', this.idx++)
return null
// throw new Error('Method not implemented.')
}
public async findAll(query?: Partial<APIKey> | undefined): Promise<APIKey[]> {
throw new Error('Method not implemented.')
}
public async update(obj: APIKey): Promise<APIKey | null> {
throw new Error('Method not implemented.')
}
public async delete(obj: APIKey): Promise<boolean> {
throw new Error('Method not implemented.')
}
}

View File

@ -0,0 +1,4 @@
export default interface APIKey {
id: string
user: string
}

18
src/models/Client.ts Normal file
View File

@ -0,0 +1,18 @@
import { MongoClient } from 'mongodb'
import mongoose from 'mongoose'
export default class Client {
private static connectionString = import.meta.env.MONGODB
private static client = false
public static async get() {
if (!this.connectionString) {
throw new Error('Can\'t connect to the database, missing the connection string')
}
if (!this.client) {
mongoose.connect(this.connectionString)
this.client = true
}
return this.client
}
}

View File

@ -0,0 +1,21 @@
import type Config from '.'
import Dao from '../Dao'
export default class ConfigDao extends Dao<Config> {
private idx = 0
public async create(obj: Omit<Config, 'id'>): Promise<Config | null> {
console.log('pouet', this.idx++)
return null
// throw new Error('Method not implemented.')
}
public async findAll(query?: Partial<Config> | undefined): Promise<Config[]> {
throw new Error('Method not implemented.')
}
public async update(obj: Config): Promise<Config | null> {
throw new Error('Method not implemented.')
}
public async delete(obj: Config): Promise<boolean> {
throw new Error('Method not implemented.')
}
}

View File

@ -0,0 +1,6 @@
import type User from '../User'
export default interface Config {
id: string
user: string
}

105
src/models/Dao.ts Normal file
View File

@ -0,0 +1,105 @@
import Client from './Client'
/**
* the Dao is the object that connect the Database or source to the application layer
*
* you MUST call it through the `DaoFactory` file
*/
export default abstract class Dao<Object extends { id: any } = { id: any }> {
/**
* insert a new object into the source
*
* @param obj the object to create
* @returns the object with it's id filled if create or null otherwise
*/
abstract create(obj: Omit<Object, 'id'>): Promise<Object | null>
/**
* insert a new object into the source
*
* @param obj the object to create
* @returns the object with it's id filled if create or null otherwise
*/
public insert: Dao<Object>['create'] = (obj: Parameters<Dao<Object>['create']>[0]) => this.create(obj)
/**
* find the list of objects having elements from the query
*
* @param query a partial object which filter depending on the elements, if not set it will fetch everything
* @returns an array containing the list of elements that match with the query
*/
abstract findAll(query?: Partial<Object>): Promise<Array<Object>>
/**
* find the list of objects having elements from the query
*
* @param query a partial object which filter depending on the elements, if not set it will fetch everything
* @returns an array containing the list of elements that match with the query
*/
public find: Dao<Object>['findAll'] = (query: Parameters<Dao<Object>['findAll']>[0]) => this.findAll(query)
/**
* find an object by it's id
*
* (shortcut to findOne({id: id}))
*
* @param id the id of the object
* @returns
*/
public findById(id: Object['id']): Promise<Object | null> {
return this.findOne({id: id} as Partial<Object>)
}
/**
* find an object by it's id
*
* (shortcut to findOne({id: id}))
*
* @param id the id of the object
* @returns
*/
public get(id: Object['id']) {
return this.findById(id)
}
/**
* find the first element that match `query`
*
* @param query a partial object which filter depending on the elements, if not set it will fetch everything
* @returns the first element matching with the query or null otherwise
*/
public async findOne(query?: Partial<Object>): Promise<Object | null> {
return (await this.findAll(query))[0] ?? null
}
/**
* update the remote reference of the object
*
* note: it will not try to insert an item (use `upsert` to handle this)
*
* @param obj the object to update
* @returns an object if it was able to update or null otherwise
*/
abstract update(obj: Object): Promise<Object | null>
/**
* update the remote reference of the object or create it if not found
* @param obj the object to update/insert
* @returns the object is updated/inserted or null otherwise
*/
public async upsert(object: Object | Omit<Object, 'id'>): Promise<Object | null> {
if ('id' in object) {
return this.update(object)
}
return this.insert(object)
}
/**
* Delete the object
* @param obj the object to delete
*
* @returns if the object was deleted or not (if object is not in db it will return true)
*/
abstract delete(obj: Object): Promise<boolean>
}

63
src/models/DaoFactory.ts Normal file
View File

@ -0,0 +1,63 @@
import Client from './Client'
import ConfigDao from './Config/ConfigDao'
import Dao from './Dao'
import UserDao from './User/UserDao'
/**
* TODO:
* Add to `DaoItem` your model name
* Add to the function `initDao` the Dao
*/
/**
* the different Daos that can be initialized
*
* Touch this interface to define which key is linked to which Dao
*/
interface DaoItem {
config: ConfigDao
user: UserDao
}
/**
* Class to get any DAO
*/
export default class DaoFactory {
/**
* reference of the different Daos for a correct singleton implementation
*/
private static daos: Partial<DaoItem> = {}
/**
* Get a a dao by its key
*
* it will throw an error if no Dao exists linked to the item key
*
* @param key the dao key to get
* @returns the Dao you want as a singleton
*/
public static get<Key extends keyof DaoItem>(key: Key): DaoItem[Key] {
if (!(key in this.daos)) {
const dao = this.initDao(key)
if (!dao) {
throw new Error(`${key} has no valid Dao`)
}
this.daos[key] = dao as DaoItem[Key]
}
return this.daos[key] as DaoItem[Key]
}
/**
* init a dao by its key, it does not care if it exists or not
*
* @param item the element to init
* @returns a new initialized dao or undefined if no dao is linked
*/
private static initDao(item: keyof DaoItem): Dao<any> | undefined {
switch (item) {
case 'config': return new ConfigDao()
case 'user': return new UserDao()
default: return undefined
}
}
}

26
src/models/README.md Normal file
View File

@ -0,0 +1,26 @@
# Models
this folder contains the Application data layer
## Workflow
1. Add a {model}/index.ts contianing your `interface`
2. Add a {model}/{model}Dao.ts containing your DAO that `extends` from `Dao.ts`
3. Add your Dao to the `DaoFactory.ts` file
## **/index.ts
file containing the definition of the model
## **/\*Dao.ts
File containing the implementation of the Dao
## Dao.ts
the Dao.ts is the file each `*Dao.ts` extends from allowing to have a simple, quick and easy to comprehend connectivity
## DaoFactory.ts
The DaoFactory file is the file in which you will have the only direct reference to each `*Dao` files and will be sent from there to the rest of the applicaiton layer

View File

@ -0,0 +1,68 @@
import { objectOmit } from '@dzeio/object-util'
import mongoose, { ObjectId } from 'mongoose'
import User from '.'
import Client from '../Client'
import Dao from '../Dao'
export default class UserDao extends Dao<User> {
private model = mongoose.model('User', new mongoose.Schema({
email: { type: String, required: true }
}, {
timestamps: true
}))
private collection = 'users'
private idx = 0
public async create(obj: Omit<User, 'id' | 'created' | 'updated'>): Promise<User | null> {
await Client.get()
return this.fromSource(await this.model.create(obj))
}
public async findAll(query?: Partial<User> | undefined): Promise<User[]> {
await Client.get()
if (query?.id) {
const item = await this.model.findById(new mongoose.Types.ObjectId(query.id))
if (!item) {
return []
}
return [this.fromSource(item)]
}
const resp = await this.model.find(query ? this.toSource(query as User) : {})
return resp.map(this.fromSource)
}
public async update(obj: User): Promise<User | null> {
await Client.get()
const query = await this.model.updateOne({
_id: new mongoose.Types.ObjectId(obj.id)
}, this.toSource(obj))
if (query.matchedCount >= 1) {
obj.updated = new Date()
return obj
}
return null
// return this.fromSource()
}
public async delete(obj: User): Promise<boolean> {
await Client.get()
const res = await this.model.deleteOne({
_id: new mongoose.Types.ObjectId(obj.id)
})
return res.deletedCount > 0
}
private toSource(obj: User): Omit<User, 'id'> {
return objectOmit(obj, 'id', 'updated', 'created')
}
private fromSource(doc: mongoose.Document<any, any, User>): User {
return {
id: doc._id.toString(),
email: doc.get('email'),
updated: doc.get('updatedAt'),
created: doc.get('createdAt')
}
}
}

6
src/models/User/index.ts Normal file
View File

@ -0,0 +1,6 @@
export default interface User {
id: string
email: string
created: Date
updated: Date
}