diff --git a/apps/teloalapi/src/models/User.model.ts b/apps/teloalapi/src/models/User.model.ts new file mode 100644 index 0000000..3a56cd1 --- /dev/null +++ b/apps/teloalapi/src/models/User.model.ts @@ -0,0 +1,39 @@ +import { Entity, model, property } from "@loopback/repository"; +import { User, UserRole } from "../types/User"; + +@model({ + settings: { + description: "A TeloAL user. Created when registering and used when authenticating.", + }, +}) +export class UserModel extends Entity implements User { + @property({ + id: true, + jsonSchema: { + description: "Uniquely identifies a user.", + }, + }) + username: string; + + @property({ + jsonSchema: { + description: "Optional email address of the user.", + }, + }) + email?: string; + + @property({ + jsonSchema: { + description: "Hashed user password.", + }, + }) + password: string; + + @property({ + jsonSchema: { + description: "Hashed user password.", + enum: ["admin", "user"], + }, + }) + role: UserRole; +} diff --git a/apps/teloalapi/src/repositories/AutoDateRepository.ts b/apps/teloalapi/src/repositories/AutoDateRepository.ts new file mode 100644 index 0000000..6aa566c --- /dev/null +++ b/apps/teloalapi/src/repositories/AutoDateRepository.ts @@ -0,0 +1,109 @@ +import { + Count, + DataObject, + DefaultCrudRepository, + Entity, + JugglerDataSource, + Options, + Where, + model, + property, +} from "@loopback/repository"; + +type Timestamps = { + createdAt?: Date; + updatedAt?: Date; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type GConstructor = new (...args: any[]) => T; + +type TimestampedModel = TModel & Timestamps; + +function extendClassWithTimestamps>(Base: T, modelName: string) { + @model({ + name: modelName, + }) + class Extended extends Base { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...args: any[]) { + super(...args); + } + + @property() + createdAt?: Date; + + @property() + updatedAt?: Date; + } + + return Extended; +} + +export function makeRepository( + TheModel: GConstructor, + modelName: string, + idProp: keyof TModel, +) { + type TimestampedTModel = TimestampedModel; + + class AutoDateRepository extends DefaultCrudRepository { + constructor(dataSource: JugglerDataSource) { + // @ts-ignore + super(extendClassWithTimestamps>(TheModel, modelName), dataSource); + } + + async create( + data: DataObject, + options?: Options, + ): Promise { + data.createdAt = new Date(); + data.updatedAt = new Date(); + return super.create(data, options); + } + + async update(data: TimestampedTModel, options?: Options): Promise { + data.updatedAt = new Date(); + return super.update(data, options); + } + + async updateAll( + data: DataObject, + where?: Where, + options?: Options, + ): Promise { + data.updatedAt = new Date(); + return super.updateAll(data, where, options); + } + + async replaceById( + id: ID, + data: DataObject, + options?: Options, + ): Promise { + data.updatedAt = new Date(); + return super.replaceById(id, data, options); + } + + async updateById( + id: ID, + data: DataObject, + options?: Options, + ): Promise { + data.updatedAt = new Date(); + return super.updateById(id, data, options); + } + + async upsert(data: DataObject) { + const id = data[idProp] as ID; + + if (await this.exists(id)) { + return this.updateById(id, data); + } + + return this.create(data); + } + } + + return AutoDateRepository; +} diff --git a/apps/teloalapi/src/repositories/Users.repository.ts b/apps/teloalapi/src/repositories/Users.repository.ts new file mode 100644 index 0000000..023103a --- /dev/null +++ b/apps/teloalapi/src/repositories/Users.repository.ts @@ -0,0 +1,16 @@ +import { inject } from "@loopback/core"; +import { MongoDataSource } from "../datasources/mongo.datasource"; +import { UserModel } from "../models/User.model"; +import { makeRepository } from "./AutoDateRepository"; + +const BaseRepository = makeRepository( + UserModel, + "User", + "username", +); + +export class UsersRepository extends BaseRepository { + constructor(@inject("datasources.mongo") dataSource: MongoDataSource) { + super(dataSource); + } +} diff --git a/apps/teloalapi/src/repositories/alCharacter.repository.ts b/apps/teloalapi/src/repositories/alCharacter.repository.ts index be05f36..1fb2382 100644 --- a/apps/teloalapi/src/repositories/alCharacter.repository.ts +++ b/apps/teloalapi/src/repositories/alCharacter.repository.ts @@ -1,93 +1,16 @@ +import { AlCharacter } from "@teloal/parse-character"; import { inject } from "@loopback/core"; -import { - Count, - DefaultCrudRepository, - Entity, - Options, - Where, - model, - property, -} from "@loopback/repository"; import { MongoDataSource } from "../datasources/mongo.datasource"; -import { AlCharacter } from "@teloal/parse-character"; - -type Timestamps = { - createdAt?: Date; - updatedAt?: Date; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type GConstructor = new (...args: any[]) => T; - -function extendClassWithTimestamps>(Base: T) { - @model({ - name: "AlCharacter", - }) - class Extended extends Base { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - constructor(...args: any[]) { - super(...args); - } - - @property({ - type: "date", - }) - createdAt?: Date; +import { makeRepository } from "./AutoDateRepository"; - @property({ - type: "date", - }) - updatedAt?: Date; - } - - return Extended; -} +const BaseRepository = makeRepository( + AlCharacter, + "AlCharacter", + "name", +); -type TimestampedAlCharacter = AlCharacter & Timestamps; - -export class AlCharacterRepository extends DefaultCrudRepository< - TimestampedAlCharacter, - typeof AlCharacter.prototype.name -> { +export class AlCharacterRepository extends BaseRepository { constructor(@inject("datasources.mongo") dataSource: MongoDataSource) { - super(extendClassWithTimestamps(AlCharacter), dataSource); - } - - async create(entity: TimestampedAlCharacter, options?: Options): Promise { - entity.createdAt = new Date(); - entity.updatedAt = new Date(); - return super.create(entity, options); - } - - async update(data: TimestampedAlCharacter, options?: Options): Promise { - data.updatedAt = new Date(); - return super.update(data, options); - } - - async updateAll( - data: TimestampedAlCharacter, - where?: Where, - options?: Options, - ): Promise { - data.updatedAt = new Date(); - return super.updateAll(data, where, options); - } - - async replaceById(id: string, data: TimestampedAlCharacter, options?: Options): Promise { - data.updatedAt = new Date(); - return super.replaceById(id, data, options); - } - - async updateById(id: string, data: TimestampedAlCharacter, options?: Options): Promise { - data.updatedAt = new Date(); - return super.updateById(id, data, options); - } - - async upsert(char: AlCharacter) { - if (await this.exists(char.name)) { - return this.updateById(char.name, char); - } - - return this.create(char); + super(dataSource); } } diff --git a/apps/teloalapi/src/types/User.ts b/apps/teloalapi/src/types/User.ts new file mode 100644 index 0000000..2dffab7 --- /dev/null +++ b/apps/teloalapi/src/types/User.ts @@ -0,0 +1,13 @@ +export enum UserRole { + user = "user", + admin = "admin", +} + +export type User = { + username: string; + email?: string; + password: string; + role: UserRole; + createdAt?: Date; + updatedAt?: Date; +}; diff --git a/apps/teloalapi/tsconfig.app.json b/apps/teloalapi/tsconfig.app.json index 2dd4cb8..b991896 100644 --- a/apps/teloalapi/tsconfig.app.json +++ b/apps/teloalapi/tsconfig.app.json @@ -16,7 +16,7 @@ "moduleResolution": "node", "target": "es2018", "sourceMap": true, - "declaration": true, + "declaration": false, "importHelpers": true, "types": ["node"] }, diff --git a/docker-compose.yml b/docker-compose.yml index fba2238..b8a61fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ version: "3" services: teloal-mongo: - image: mongo + image: mongo:7 container_name: teloal-mongo restart: unless-stopped environment: @@ -17,7 +17,7 @@ services: - ./scripts/init-mongo.js:/docker-entrypoint-initdb.d/mongo-init.js:ro teloal-mongo-express: - image: mongo-express + image: mongo-express:1.0.0-18-alpine3.18 container_name: teloal-mongo-express restart: unless-stopped environment: