diff --git a/src/app.module.ts b/src/app.module.ts index 8371dae..9222f4e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,7 @@ import { AirportController } from './airport/airport.controller'; import { InboundModule } from './inbound/inbound.module'; import { InboundController } from './inbound/inbound.controller'; import { WeatherModule } from './weather/weather.module'; +import { UserModule } from './user/user.module'; @Module({ imports: [ @@ -35,6 +36,7 @@ import { WeatherModule } from './weather/weather.module'; LoggingModule, InboundModule, WeatherModule, + UserModule, ], controllers: [LoggingController, AirportController, InboundController], }) diff --git a/src/user/models/user.model.ts b/src/user/models/user.model.ts new file mode 100644 index 0000000..03475b3 --- /dev/null +++ b/src/user/models/user.model.ts @@ -0,0 +1,39 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document } from 'mongoose'; + +export type UserDocument = User & Document; + +@Schema() +export class User { + @Prop({ + required: true, + index: true, + type: Number, + }) + vatsimId: number; + + @Prop({ + required: true, + type: String, + }) + apiCode: string; + + @Prop({ + type: Boolean, + default: false, + }) + systemAdminAccess: boolean; + + @Prop({ + type: [String], + default: [], + }) + airportAdminAccess: string[]; + + @Prop({ + type: String, + }) + fullName: string; +} + +export const UserSchema = SchemaFactory.createForClass(User); diff --git a/src/user/user.module.ts b/src/user/user.module.ts new file mode 100644 index 0000000..e66ab04 --- /dev/null +++ b/src/user/user.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { UserSchema } from './models/user.model'; +import { UserService } from './user.service'; + +@Module({ + imports: [MongooseModule.forFeature([{ name: 'user', schema: UserSchema }])], + providers: [UserService], + exports: [MongooseModule, UserService], +}) +export class UserModule {} diff --git a/src/user/user.service.spec.ts b/src/user/user.service.spec.ts new file mode 100644 index 0000000..873de8a --- /dev/null +++ b/src/user/user.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UserService } from './user.service'; + +describe('UserService', () => { + let service: UserService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [UserService], + }).compile(); + + service = module.get(UserService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/user/user.service.ts b/src/user/user.service.ts new file mode 100644 index 0000000..18dcb70 --- /dev/null +++ b/src/user/user.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { randomBytes } from 'crypto'; +import { Model } from 'mongoose'; +import { UserDocument, User } from './models/user.model'; + +const ApiKeyLength = 64; + +@Injectable() +export class UserService { + constructor( + @InjectModel('user') + private readonly userModel: Model, + ) {} + + private static createApiKey(): string { + return randomBytes(ApiKeyLength).toString('hex'); + } + + async register(vatsimId: number, fullName: string): Promise { + return this.user(vatsimId).then((user) => { + if (user) return user; + + return this.userModel + .create({ + vatsimId, + apiCode: UserService.createApiKey(), + systemAdminAccess: false, + airportAdminAccess: [], + fullName, + }) + .then(() => this.user(vatsimId)); + }); + } + + async user(vatsimId: number): Promise { + return this.userModel.findOne({ vatsimId }); + } + + async refreshApiKey(vatsimId: number): Promise { + return this.userModel + .findOneAndUpdate({ vatsimId }, { apiCode: UserService.createApiKey() }) + .then(() => + this.user(vatsimId).then((user) => { + if (!user) return undefined; + return user; + }), + ); + } +}