add user preferences endpoint

This commit is contained in:
rubikscraft
2022-04-13 17:28:48 +02:00
parent 2bbe798097
commit 5632eaffdf
12 changed files with 155 additions and 40 deletions

View File

@@ -2,20 +2,23 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { EarlyConfigModule } from '../../config/early/earlyconfig.module'; import { EarlyConfigModule } from '../../config/early/earlyconfig.module';
import { ESysPreferenceBackend } from '../../models/entities/syspreference.entity'; import { ESysPreferenceBackend } from '../../models/entities/syspreference.entity';
import { EUsrPreferenceBackend } from '../../models/entities/usrpreference.entity';
import { PreferenceCommonService } from './preferencecommon.service'; import { PreferenceCommonService } from './preferencecommon.service';
import { PreferenceDefaultsService } from './preferencedefaults.service'; import { PreferenceDefaultsService } from './preferencedefaults.service';
import { SysPreferenceService } from './syspreferencedb.service'; import { SysPreferenceService } from './syspreferencedb.service';
import { UsrPreferenceService } from './usrpreferencedb.service';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([ESysPreferenceBackend]), TypeOrmModule.forFeature([ESysPreferenceBackend, EUsrPreferenceBackend]),
EarlyConfigModule, EarlyConfigModule,
], ],
providers: [ providers: [
SysPreferenceService, SysPreferenceService,
UsrPreferenceService,
PreferenceDefaultsService, PreferenceDefaultsService,
PreferenceCommonService, PreferenceCommonService,
], ],
exports: [SysPreferenceService], exports: [SysPreferenceService, UsrPreferenceService],
}) })
export class SysPreferenceModule {} export class PreferenceModule {}

View File

@@ -43,7 +43,7 @@ export class UsrPreferenceService {
try { try {
// Upsert here, because we want to create a new record if it does not exist // Upsert here, because we want to create a new record if it does not exist
await this.usrPreferenceRepository.upsert(usrPreference, { await this.usrPreferenceRepository.upsert(usrPreference, {
conflictPaths: ['key', 'user'], conflictPaths: ['key', 'userId'],
}); });
} catch (e: any) { } catch (e: any) {
this.logger.warn(e); this.logger.warn(e);

View File

@@ -5,7 +5,7 @@ import { generateRandomString } from 'picsur-shared/dist/util/random';
import { AuthConfigService } from '../../config/early/auth.config.service'; import { AuthConfigService } from '../../config/early/auth.config.service';
import { EarlyConfigModule } from '../../config/early/earlyconfig.module'; import { EarlyConfigModule } from '../../config/early/earlyconfig.module';
import { EUserBackend } from '../../models/entities/user.entity'; import { EUserBackend } from '../../models/entities/user.entity';
import { SysPreferenceModule } from '../preferencesdb/preferencedb.module'; import { PreferenceModule } from '../preferencesdb/preferencedb.module';
import { RolesModule } from '../roledb/roledb.module'; import { RolesModule } from '../roledb/roledb.module';
import { UsersService } from './userdb.service'; import { UsersService } from './userdb.service';
@@ -13,7 +13,7 @@ import { UsersService } from './userdb.service';
imports: [ imports: [
EarlyConfigModule, EarlyConfigModule,
RolesModule, RolesModule,
SysPreferenceModule, PreferenceModule,
TypeOrmModule.forFeature([EUserBackend]), TypeOrmModule.forFeature([EUserBackend]),
], ],
providers: [UsersService], providers: [UsersService],

View File

@@ -1,5 +1,5 @@
import { Logger, Module, OnModuleInit } from '@nestjs/common'; import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { SysPreferenceModule } from '../../collections/preferencesdb/preferencedb.module'; import { PreferenceModule } from '../../collections/preferencesdb/preferencedb.module';
import { SysPreferenceService } from '../../collections/preferencesdb/syspreferencedb.service'; import { SysPreferenceService } from '../../collections/preferencesdb/syspreferencedb.service';
import { EarlyConfigModule } from '../early/earlyconfig.module'; import { EarlyConfigModule } from '../early/earlyconfig.module';
import { EarlyJwtConfigService } from '../early/earlyjwt.config.service'; import { EarlyJwtConfigService } from '../early/earlyjwt.config.service';
@@ -11,7 +11,7 @@ import { JwtConfigService } from './jwt.config.service';
// Otherwise we will create a circular depedency // Otherwise we will create a circular depedency
@Module({ @Module({
imports: [SysPreferenceModule, EarlyConfigModule], imports: [PreferenceModule, EarlyConfigModule],
providers: [JwtConfigService], providers: [JwtConfigService],
exports: [JwtConfigService, EarlyConfigModule], exports: [JwtConfigService, EarlyConfigModule],
}) })

View File

@@ -0,0 +1,18 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import AuthFasityRequest from '../models/requests/authrequest.dto';
export const ReqUser = createParamDecorator(
(input: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest() as AuthFasityRequest;
return request.user;
},
);
export const ReqUserID = createParamDecorator(
(input: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest() as AuthFasityRequest;
const id = request.user.id;
if (!id) throw new Error('User ID is not set');
return id;
},
);

View File

@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { SysPreferenceModule } from '../../collections/preferencesdb/preferencedb.module'; import { PreferenceModule } from '../../collections/preferencesdb/preferencedb.module';
import { UsersModule } from '../../collections/userdb/userdb.module'; import { UsersModule } from '../../collections/userdb/userdb.module';
import { JwtConfigService, JwtSecretProvider } from '../../config/late/jwt.config.service'; import { JwtConfigService, JwtSecretProvider } from '../../config/late/jwt.config.service';
import { LateConfigModule } from '../../config/late/lateconfig.module'; import { LateConfigModule } from '../../config/late/lateconfig.module';
@@ -15,7 +15,7 @@ import { GuestService } from './guest.service';
imports: [ imports: [
UsersModule, UsersModule,
PassportModule, PassportModule,
SysPreferenceModule, PreferenceModule,
LateConfigModule, LateConfigModule,
JwtModule.registerAsync({ JwtModule.registerAsync({
useExisting: JwtConfigService, useExisting: JwtConfigService,

View File

@@ -1,5 +1,5 @@
import { IsEntityID } from 'picsur-shared/dist/validators/entity-id.validator'; import { IsEntityID } from 'picsur-shared/dist/validators/entity-id.validator';
import { Column, Index, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { Column, Entity, Index, PrimaryGeneratedColumn, Unique } from 'typeorm';
import z from 'zod'; import z from 'zod';
export const EUsrPreferenceSchema = z.object({ export const EUsrPreferenceSchema = z.object({
@@ -10,6 +10,7 @@ export const EUsrPreferenceSchema = z.object({
}); });
type EUsrPreference = z.infer<typeof EUsrPreferenceSchema>; type EUsrPreference = z.infer<typeof EUsrPreferenceSchema>;
@Entity()
@Unique(['key', 'userId']) @Unique(['key', 'userId'])
export class EUsrPreferenceBackend implements EUsrPreference { export class EUsrPreferenceBackend implements EUsrPreference {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')

View File

@@ -1,12 +1,26 @@
import { Controller, Get, Request } from '@nestjs/common'; import {
Controller, Get,
Request
} from '@nestjs/common';
import { UserInfoResponse } from 'picsur-shared/dist/dto/api/usermanage.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.dto';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { ReqUserID } from '../../../decorators/request-user.decorator';
import { Returns } from '../../../decorators/returns.decorator';
import AuthFasityRequest from '../../../models/requests/authrequest.dto'; import AuthFasityRequest from '../../../models/requests/authrequest.dto';
@Controller('api/experiment') @Controller('api/experiment')
@RequiredPermissions(Permission.Settings)
export class ExperimentController { export class ExperimentController {
@Get() @Get()
async testRoute(@Request() req: AuthFasityRequest) { @Returns(UserInfoResponse)
return { async testRoute(
message: req.user, @Request() req: AuthFasityRequest,
}; @ReqUserID() thing: string,
): Promise<UserInfoResponse> {
return req.user;
} }
} }

View File

@@ -1,9 +1,10 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { SysPreferenceModule } from '../../../collections/preferencesdb/preferencedb.module'; import { PreferenceModule } from '../../../collections/preferencesdb/preferencedb.module';
import { PrefController } from './pref.controller'; import { SysPrefController } from './syspref.controller';
import { UsrPrefController } from './usrpref.controller';
@Module({ @Module({
imports: [SysPreferenceModule], imports: [PreferenceModule],
controllers: [PrefController], controllers: [SysPrefController, UsrPrefController],
}) })
export class PrefModule {} export class PrefModule {}

View File

@@ -19,14 +19,14 @@ import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
import { Permission } from '../../../models/dto/permissions.dto'; import { Permission } from '../../../models/dto/permissions.dto';
@Controller('api/pref') @Controller('api/pref/sys')
@RequiredPermissions(Permission.SysPrefManage) @RequiredPermissions(Permission.SysPrefManage)
export class PrefController { export class SysPrefController {
private readonly logger = new Logger('PrefController'); private readonly logger = new Logger('SysPrefController');
constructor(private prefService: SysPreferenceService) {} constructor(private prefService: SysPreferenceService) {}
@Get('sys') @Get()
@Returns(MultipleSysPreferencesResponse) @Returns(MultipleSysPreferencesResponse)
async getAllSysPrefs(): Promise<MultipleSysPreferencesResponse> { async getAllSysPrefs(): Promise<MultipleSysPreferencesResponse> {
const prefs = await this.prefService.getAllPreferences(); const prefs = await this.prefService.getAllPreferences();
@@ -41,7 +41,7 @@ export class PrefController {
}; };
} }
@Get('sys/:key') @Get(':key')
@Returns(GetSysPreferenceResponse) @Returns(GetSysPreferenceResponse)
async getSysPref( async getSysPref(
@Param('key') key: string, @Param('key') key: string,
@@ -55,7 +55,7 @@ export class PrefController {
return pref; return pref;
} }
@Post('sys/:key') @Post(':key')
@Returns(UpdateSysPreferenceResponse) @Returns(UpdateSysPreferenceResponse)
async setSysPref( async setSysPref(
@Param('key') key: string, @Param('key') key: string,

View File

@@ -0,0 +1,81 @@
import {
Body,
Controller,
Get,
InternalServerErrorException,
Logger,
Param,
Post
} from '@nestjs/common';
import {
GetSysPreferenceResponse,
MultipleSysPreferencesResponse,
UpdateSysPreferenceRequest,
UpdateSysPreferenceResponse
} from 'picsur-shared/dist/dto/api/syspref.dto';
import { HasFailed } from 'picsur-shared/dist/types';
import { UsrPreferenceService } from '../../../collections/preferencesdb/usrpreferencedb.service';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { ReqUserID } from '../../../decorators/request-user.decorator';
import { Returns } from '../../../decorators/returns.decorator';
import { Permission } from '../../../models/dto/permissions.dto';
@Controller('api/pref/usr')
@RequiredPermissions(Permission.Settings)
export class UsrPrefController {
private readonly logger = new Logger('UsrPrefController');
constructor(private prefService: UsrPreferenceService) {}
@Get()
@Returns(MultipleSysPreferencesResponse)
async getAllSysPrefs(@ReqUserID() userid: string): Promise<MultipleSysPreferencesResponse> {
const prefs = await this.prefService.getAllPreferences(userid);
if (HasFailed(prefs)) {
this.logger.warn(prefs.getReason());
throw new InternalServerErrorException('Could not get preferences');
}
return {
preferences: prefs,
total: prefs.length,
};
}
@Get(':key')
@Returns(GetSysPreferenceResponse)
async getSysPref(
@Param('key') key: string,
@ReqUserID() userid: string
): Promise<GetSysPreferenceResponse> {
const pref = await this.prefService.getPreference(userid, key);
if (HasFailed(pref)) {
this.logger.warn(pref.getReason());
throw new InternalServerErrorException('Could not get preference');
}
return pref;
}
@Post(':key')
@Returns(UpdateSysPreferenceResponse)
async setSysPref(
@Param('key') key: string,
@ReqUserID() userid: string,
@Body() body: UpdateSysPreferenceRequest,
): Promise<UpdateSysPreferenceResponse> {
const value = body.value;
const pref = await this.prefService.setPreference(userid, key, value);
if (HasFailed(pref)) {
this.logger.warn(pref.getReason());
throw new InternalServerErrorException('Could not set preference');
}
return {
key,
value: pref.value,
type: pref.type,
};
}
}

View File

@@ -4,15 +4,16 @@ import {
Get, Get,
InternalServerErrorException, InternalServerErrorException,
Logger, Logger,
Post, Post
Request
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
UserLoginResponse, UserMePermissionsResponse, UserLoginResponse,
UserMePermissionsResponse,
UserMeResponse, UserMeResponse,
UserRegisterRequest, UserRegisterRequest,
UserRegisterResponse UserRegisterResponse
} from 'picsur-shared/dist/dto/api/user.dto'; } from 'picsur-shared/dist/dto/api/user.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed } from 'picsur-shared/dist/types'; import { HasFailed } from 'picsur-shared/dist/types';
import { UsersService } from '../../../collections/userdb/userdb.service'; import { UsersService } from '../../../collections/userdb/userdb.service';
import { import {
@@ -20,10 +21,10 @@ import {
RequiredPermissions, RequiredPermissions,
UseLocalAuth UseLocalAuth
} from '../../../decorators/permissions.decorator'; } from '../../../decorators/permissions.decorator';
import { ReqUser, ReqUserID } from '../../../decorators/request-user.decorator';
import { Returns } from '../../../decorators/returns.decorator'; import { Returns } from '../../../decorators/returns.decorator';
import { AuthManagerService } from '../../../managers/auth/auth.service'; import { AuthManagerService } from '../../../managers/auth/auth.service';
import { Permission } from '../../../models/dto/permissions.dto'; import { Permission } from '../../../models/dto/permissions.dto';
import AuthFasityRequest from '../../../models/requests/authrequest.dto';
import { EUserBackend2EUser } from '../../../models/transformers/user.transformer'; import { EUserBackend2EUser } from '../../../models/transformers/user.transformer';
@Controller('api/user') @Controller('api/user')
@@ -38,8 +39,8 @@ export class UserController {
@Post('login') @Post('login')
@Returns(UserLoginResponse) @Returns(UserLoginResponse)
@UseLocalAuth(Permission.UserLogin) @UseLocalAuth(Permission.UserLogin)
async login(@Request() req: AuthFasityRequest): Promise<UserLoginResponse> { async login(@ReqUser() user: EUser): Promise<UserLoginResponse> {
const jwt_token = await this.authService.createToken(req.user); const jwt_token = await this.authService.createToken(user);
if (HasFailed(jwt_token)) { if (HasFailed(jwt_token)) {
this.logger.warn(jwt_token.getReason()); this.logger.warn(jwt_token.getReason());
throw new InternalServerErrorException('Could not get new token'); throw new InternalServerErrorException('Could not get new token');
@@ -69,10 +70,8 @@ export class UserController {
@Get('me') @Get('me')
@Returns(UserMeResponse) @Returns(UserMeResponse)
@RequiredPermissions(Permission.UserKeepLogin) @RequiredPermissions(Permission.UserKeepLogin)
async me(@Request() req: AuthFasityRequest): Promise<UserMeResponse> { async me(@ReqUserID() userid: string): Promise<UserMeResponse> {
if (!req.user.id) throw new InternalServerErrorException('User is corrupt'); const backenduser = await this.usersService.findOne(userid);
const backenduser = await this.usersService.findOne(req.user.id);
if (HasFailed(backenduser)) { if (HasFailed(backenduser)) {
this.logger.warn(backenduser.getReason()); this.logger.warn(backenduser.getReason());
@@ -95,11 +94,9 @@ export class UserController {
@Returns(UserMePermissionsResponse) @Returns(UserMePermissionsResponse)
@NoPermissions() @NoPermissions()
async refresh( async refresh(
@Request() req: AuthFasityRequest, @ReqUserID() userid: string,
): Promise<UserMePermissionsResponse> { ): Promise<UserMePermissionsResponse> {
if (!req.user.id) throw new InternalServerErrorException('User is corrupt'); const permissions = await this.usersService.getPermissions(userid);
const permissions = await this.usersService.getPermissions(req.user.id);
if (HasFailed(permissions)) { if (HasFailed(permissions)) {
this.logger.warn(permissions.getReason()); this.logger.warn(permissions.getReason());
throw new InternalServerErrorException('Could not get permissions'); throw new InternalServerErrorException('Could not get permissions');