Feature/adc/#50 users settings route (#89)

* #50 - migration 20221023123919_ + route settings

* #50 - migration 20221023123919_ + route settings

* #50 - settings creation at user creation + update migration

* changed settings acces from by id to userId

* deleting the user results in deleting it's associated userSettings row

* pr fixes + robot tests + other minor fixes

* removed useless comments

* added settings endpoint to /auth/me and automated creation to /register

* clean code before merge
This commit is contained in:
Amaury
2023-04-12 05:32:41 +03:00
committed by GitHub
parent e43a8fd111
commit a26efefd01
15 changed files with 278 additions and 16 deletions
@@ -0,0 +1,20 @@
-- CreateTable
CREATE TABLE "UserSettings" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"pushNotification" BOOLEAN NOT NULL DEFAULT true,
"emailNotification" BOOLEAN NOT NULL DEFAULT true,
"trainingNotification" BOOLEAN NOT NULL DEFAULT true,
"newsongNotification" BOOLEAN NOT NULL DEFAULT true,
"dataCollection" BOOLEAN NOT NULL DEFAULT true,
"CustomAdds" BOOLEAN NOT NULL DEFAULT true,
"Recommendations" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "UserSettings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "UserSettings_userId_key" ON "UserSettings"("userId");
-- AddForeignKey
ALTER TABLE "UserSettings" ADD CONSTRAINT "UserSettings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -0,0 +1,25 @@
/*
Warnings:
- You are about to drop the column `CustomAdds` on the `UserSettings` table. All the data in the column will be lost.
- You are about to drop the column `Recommendations` on the `UserSettings` table. All the data in the column will be lost.
- You are about to drop the column `dataCollection` on the `UserSettings` table. All the data in the column will be lost.
- You are about to drop the column `newsongNotification` on the `UserSettings` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "UserSettings" DROP CONSTRAINT "UserSettings_userId_fkey";
-- AlterTable
ALTER TABLE "UserSettings" DROP COLUMN "CustomAdds",
DROP COLUMN "Recommendations",
DROP COLUMN "dataCollection",
DROP COLUMN "newsongNotification",
ADD COLUMN "leaderBoard" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "newSongNotification" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "recommendations" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "showActivity" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "weeklyReport" BOOLEAN NOT NULL DEFAULT true;
-- AddForeignKey
ALTER TABLE "UserSettings" ADD CONSTRAINT "UserSettings_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+24 -9
View File
@@ -10,15 +10,30 @@ datasource db {
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String
email String
isGuest Boolean @default(false)
partyPlayed Int @default(0)
LessonHistory LessonHistory[]
SongHistory SongHistory[]
searchHistory SearchHistory[]
id Int @id @default(autoincrement())
username String @unique
password String
email String
isGuest Boolean @default(false)
partyPlayed Int @default(0)
LessonHistory LessonHistory[]
SongHistory SongHistory[]
searchHistory SearchHistory[]
settings UserSettings?
}
model UserSettings {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int @unique
pushNotification Boolean @default(true)
emailNotification Boolean @default(true)
trainingNotification Boolean @default(true)
newSongNotification Boolean @default(true)
recommendations Boolean @default(true)
weeklyReport Boolean @default(true)
leaderBoard Boolean @default(true)
showActivity Boolean @default(true)
}
model SearchHistory {
+2 -3
View File
@@ -7,13 +7,11 @@ import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
import { SongModule } from './song/song.module';
import { LessonModule } from './lesson/lesson.module';
import { ArtistController } from './artist/artist.controller';
import { SettingsModule } from './settings/settings.module';
import { ArtistService } from './artist/artist.service';
import { GenreModule } from './genre/genre.module';
import { ArtistModule } from './artist/artist.module';
import { AlbumModule } from './album/album.module';
import { SearchController } from './search/search.controller';
import { SearchService } from './search/search.service';
import { SearchModule } from './search/search.module';
import { HistoryModule } from './history/history.module';
@@ -28,6 +26,7 @@ import { HistoryModule } from './history/history.module';
ArtistModule,
AlbumModule,
SearchModule,
SettingsModule,
HistoryModule,
],
controllers: [AppController],
+34 -1
View File
@@ -10,6 +10,8 @@ import {
HttpCode,
Put,
InternalServerErrorException,
Patch,
NotFoundException,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';
@@ -28,6 +30,9 @@ import { User } from '../models/user';
import { JwtToken } from './models/jwt';
import { LoginDto } from './dto/login.dto';
import { Profile } from './dto/profile.dto';
import { Setting } from 'src/models/setting';
import { UpdateSettingDto } from 'src/settings/dto/update-setting.dto';
import { SettingsService } from 'src/settings/settings.service';
@ApiTags('auth')
@Controller('auth')
@@ -35,12 +40,15 @@ export class AuthController {
constructor(
private authService: AuthService,
private usersService: UsersService,
private settingsService: SettingsService,
) {}
@Post('register')
async register(@Body() registerDto: RegisterDto): Promise<void> {
try {
await this.usersService.createUser(registerDto);
await this.usersService.createUser(registerDto).then((user) => {
this.settingsService.createUserSetting(user.id);
});
} catch {
throw new BadRequestException();
}
@@ -105,4 +113,29 @@ export class AuthController {
deleteSelf(@Request() req: any): Promise<User> {
return this.usersService.deleteUser({ id: req.user.id });
}
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({description: 'Successfully edited settings', type: Setting})
@ApiUnauthorizedResponse({description: 'Invalid token'})
@Patch('me/settings')
udpateSettings(
@Request() req: any,
@Body() settingUserDto: UpdateSettingDto): Promise<Setting> {
return this.settingsService.updateUserSettings({
where: { userId: +req.user.id},
data: settingUserDto,
});
}
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({description: 'Successfully edited settings', type: Setting})
@ApiUnauthorizedResponse({description: 'Invalid token'})
@Get('me/settings')
async getSettings(@Request() req: any): Promise<Setting> {
const result = await this.settingsService.getUserSetting({ userId: +req.user.id });
if (!result) throw new NotFoundException();
return result;
}
}
+2
View File
@@ -8,11 +8,13 @@ import { JwtModule } from '@nestjs/jwt';
import { ConfigModule } from '@nestjs/config';
import { ConfigService } from '@nestjs/config';
import { JwtStrategy } from './jwt.strategy';
import { SettingsModule } from 'src/settings/settings.module';
@Module({
imports: [
ConfigModule,
UsersModule,
SettingsModule,
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
+24
View File
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
export class Setting {
@ApiProperty()
id: number;
@ApiProperty()
userId: number;
@ApiProperty()
pushNotification: boolean;
@ApiProperty()
emailNotification: boolean;
@ApiProperty()
trainingNotification: boolean;
@ApiProperty()
newSongNotification: boolean;
@ApiProperty()
recommendations: boolean;
@ApiProperty()
weeklyReport: boolean;
@ApiProperty()
leaderBoard: boolean;
@ApiProperty()
showActivity: boolean;
}
@@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
export class UpdateSettingDto {
@ApiProperty()
pushNotification?: boolean;
@ApiProperty()
emailNotification?: boolean;
@ApiProperty()
trainingNotification?: boolean;
@ApiProperty()
newSongNotification?: boolean;
@ApiProperty()
recommendations?: boolean;
@ApiProperty()
weeklyReport?: boolean;
@ApiProperty()
leaderBoard?: boolean;
@ApiProperty()
showActivity?: boolean;
}
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SettingsController } from './settings.controller';
describe('SettingsController', () => {
let controller: SettingsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [SettingsController],
}).compile();
controller = module.get<SettingsController>(SettingsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
+10
View File
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { SettingsService } from './settings.service';
import { PrismaModule } from 'src/prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [SettingsService],
exports: [SettingsService],
})
export class SettingsModule {}
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SettingsService } from './settings.service';
describe('SettingsService', () => {
let service: SettingsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SettingsService],
}).compile();
service = module.get<SettingsService>(SettingsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+45
View File
@@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { Prisma, UserSettings } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
@Injectable()
export class SettingsService {
constructor(private prisma: PrismaService) {}
async getUserSetting(
settingWhereUniqueInput: Prisma.UserSettingsWhereUniqueInput,
): Promise<UserSettings | null> {
return this.prisma.userSettings.findUnique({
where: settingWhereUniqueInput,
});
}
async createUserSetting(userId: number): Promise<UserSettings> {
return this.prisma.userSettings.create({
data: {
user: {
connect: {
id: userId,
}
}
}
})
}
async updateUserSettings(params: {
where: Prisma.UserSettingsWhereUniqueInput;
data: Prisma.UserSettingsUpdateInput;
}): Promise<UserSettings> {
const { where, data } = params;
return this.prisma.userSettings.update({
data,
where,
});
}
async deleteUserSettings(where: Prisma.UserSettingsWhereUniqueInput): Promise<UserSettings> {
return this.prisma.userSettings.delete({
where,
});
}
}
+7 -2
View File
@@ -8,18 +8,23 @@ import {
NotFoundException,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { SettingsService } from 'src/settings/settings.service';
import { CreateUserDto } from './dto/create-user.dto';
import { ApiNotFoundResponse, ApiTags } from '@nestjs/swagger';
import { User } from 'src/models/user';
import { resolve } from 'path';
@ApiTags('users')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
constructor(private readonly usersService: UsersService, private readonly settingsService: SettingsService) {}
@Post()
create(@Body() createUserDto: CreateUserDto): Promise<User> {
return this.usersService.createUser(createUserDto);
return this.usersService.createUser(createUserDto).then((user) => {
this.settingsService.createUserSetting(user.id);
return user;
}).catch((e) => e);
}
@Get()
+2 -1
View File
@@ -2,11 +2,12 @@ import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaModule } from 'src/prisma/prisma.module';
import { SettingsService } from 'src/settings/settings.service';
@Module({
imports: [PrismaModule],
controllers: [UsersController],
providers: [UsersService],
providers: [UsersService, SettingsService],
exports: [UsersService],
})
export class UsersModule {}
+27
View File
@@ -0,0 +1,27 @@
*** Settings ***
Documentation Tests of the /settings route.
... Ensures that the settings CRUD works corectly as well as the automation with the user creation.
Resource ../rest.resource
Resource ../auth/auth.resource
*** Test Cases ***
Get settings
[Documentation] Create a user and get associated settings
${userID}= RegisterLogin 2na-min-faranssa-wa-2na-adrus-allu3'at-al3rabia
&{get}= GET /auth/me/settings/
Output
Should Be True ${get.body.emailNotification}
Integer response status 200
[Teardown] DELETE /users/${userID}
Patch settingspushNotification
${userID}= RegisterLogin 2na-min-faranssa-wa-2na-adrus-allu3'at-al3rabia
&{patch}= PATCH
... /auth/me/settings/
... {"pushNotification": true, "emailNotification": true, "trainingNotification": true, "newSongNotification": true, "recommendations": true, "weeklyReport": true, "leaderBoard": false, "showActivity": true}
Output
Should Not Be True ${patch.body.leaderBoard}
Integer response status 200
[Teardown] DELETE /users/${userID}