Use includes on liked, music, score, search and fav pages

This commit is contained in:
2023-11-29 21:45:53 +01:00
parent eff5eae706
commit c0bc611268
9 changed files with 169 additions and 237 deletions

View File

@@ -21,12 +21,12 @@ import {
Response,
Query,
Param,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';
import { LocalAuthGuard } from './local-auth.guard';
import { RegisterDto } from './dto/register.dto';
import { UsersService } from 'src/users/users.service';
} from "@nestjs/common";
import { AuthService } from "./auth.service";
import { JwtAuthGuard } from "./jwt-auth.guard";
import { LocalAuthGuard } from "./local-auth.guard";
import { RegisterDto } from "./dto/register.dto";
import { UsersService } from "src/users/users.service";
import {
ApiBadRequestResponse,
ApiBearerAuth,
@@ -36,39 +36,41 @@ import {
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';
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';
import { AuthGuard } from '@nestjs/passport';
import { FileInterceptor } from '@nestjs/platform-express';
import { writeFile } from 'fs';
import { PasswordResetDto } from './dto/password_reset.dto ';
} from "@nestjs/swagger";
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";
import { AuthGuard } from "@nestjs/passport";
import { FileInterceptor } from "@nestjs/platform-express";
import { writeFile } from "fs";
import { PasswordResetDto } from "./dto/password_reset.dto ";
import { mapInclude } from "src/utils/include";
import { SongController } from "src/song/song.controller";
@ApiTags('auth')
@Controller('auth')
@ApiTags("auth")
@Controller("auth")
export class AuthController {
constructor(
private authService: AuthService,
private usersService: UsersService,
private settingsService: SettingsService,
) {}
) { }
@Get('login/google')
@UseGuards(AuthGuard('google'))
@ApiOperation({ description: 'Redirect to google login page' })
googleLogin() {}
@Get("login/google")
@UseGuards(AuthGuard("google"))
@ApiOperation({ description: "Redirect to google login page" })
googleLogin() { }
@Get('logged/google')
@Get("logged/google")
@ApiOperation({
description:
'Redirect to the front page after connecting to the google account',
"Redirect to the front page after connecting to the google account",
})
@UseGuards(AuthGuard('google'))
@UseGuards(AuthGuard("google"))
async googleLoginCallbakc(@Req() req: any) {
let user = await this.usersService.user({ googleID: req.user.googleID });
if (!user) {
@@ -78,13 +80,13 @@ export class AuthController {
return this.authService.login(user);
}
@Post('register')
@ApiOperation({ description: 'Register a new user' })
@ApiConflictResponse({ description: 'Username or email already taken' })
@Post("register")
@ApiOperation({ description: "Register a new user" })
@ApiConflictResponse({ description: "Username or email already taken" })
@ApiOkResponse({
description: 'Successfully registered, email sent to verify',
description: "Successfully registered, email sent to verify",
})
@ApiBadRequestResponse({ description: 'Invalid data or database error' })
@ApiBadRequestResponse({ description: "Invalid data or database error" })
async register(@Body() registerDto: RegisterDto): Promise<void> {
try {
const user = await this.usersService.createUser(registerDto);
@@ -92,73 +94,73 @@ export class AuthController {
await this.authService.sendVerifyMail(user);
} catch (e) {
// check if the error is a duplicate key error
if (e.code === 'P2002') {
throw new ConflictException('Username or email already taken');
if (e.code === "P2002") {
throw new ConflictException("Username or email already taken");
}
console.error(e);
throw new BadRequestException();
}
}
@Put('verify')
@Put("verify")
@HttpCode(200)
@UseGuards(JwtAuthGuard)
@ApiOperation({ description: 'Verify the email of the user' })
@ApiOkResponse({ description: 'Successfully verified' })
@ApiBadRequestResponse({ description: 'Invalid or expired token' })
@ApiOperation({ description: "Verify the email of the user" })
@ApiOkResponse({ description: "Successfully verified" })
@ApiBadRequestResponse({ description: "Invalid or expired token" })
async verify(
@Request() req: any,
@Query('token') token: string,
@Query("token") token: string,
): Promise<void> {
if (await this.authService.verifyMail(req.user.id, token)) return;
throw new BadRequestException('Invalid token. Expired or invalid.');
throw new BadRequestException("Invalid token. Expired or invalid.");
}
@Put('reverify')
@Put("reverify")
@UseGuards(JwtAuthGuard)
@HttpCode(200)
@ApiOperation({ description: 'Resend the verification email' })
@ApiOperation({ description: "Resend the verification email" })
async reverify(@Request() req: any): Promise<void> {
const user = await this.usersService.user({ id: req.user.id });
if (!user) throw new BadRequestException('Invalid user');
if (!user) throw new BadRequestException("Invalid user");
await this.authService.sendVerifyMail(user);
}
@HttpCode(200)
@Put('password-reset')
@Put("password-reset")
async password_reset(
@Body() resetDto: PasswordResetDto,
@Query('token') token: string,
@Query("token") token: string,
): Promise<void> {
if (await this.authService.changePassword(resetDto.password, token)) return;
throw new BadRequestException('Invalid token. Expired or invalid.');
throw new BadRequestException("Invalid token. Expired or invalid.");
}
@HttpCode(200)
@Put('forgot-password')
async forgot_password(@Query('email') email: string): Promise<void> {
@Put("forgot-password")
async forgot_password(@Query("email") email: string): Promise<void> {
console.log(email);
const user = await this.usersService.user({ email: email });
if (!user) throw new BadRequestException('Invalid user');
if (!user) throw new BadRequestException("Invalid user");
await this.authService.sendPasswordResetMail(user);
}
@Post('login')
@Post("login")
@ApiBody({ type: LoginDto })
@HttpCode(200)
@UseGuards(LocalAuthGuard)
@ApiBody({ type: LoginDto })
@ApiOperation({ description: 'Login with username and password' })
@ApiOkResponse({ description: 'Successfully logged in', type: JwtToken })
@ApiUnauthorizedResponse({ description: 'Invalid credentials' })
@ApiOperation({ description: "Login with username and password" })
@ApiOkResponse({ description: "Successfully logged in", type: JwtToken })
@ApiUnauthorizedResponse({ description: "Invalid credentials" })
async login(@Request() req: any): Promise<JwtToken> {
return this.authService.login(req.user);
}
@Post('guest')
@Post("guest")
@HttpCode(200)
@ApiOperation({ description: 'Login as a guest account' })
@ApiOkResponse({ description: 'Successfully logged in', type: JwtToken })
@ApiOperation({ description: "Login as a guest account" })
@ApiOkResponse({ description: "Successfully logged in", type: JwtToken })
async guest(): Promise<JwtToken> {
const user = await this.usersService.createGuest();
await this.settingsService.createUserSetting(user.id);
@@ -167,27 +169,27 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ description: 'Get the profile picture of connected user' })
@ApiOkResponse({ description: 'The user profile picture' })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Get('me/picture')
@ApiOperation({ description: "Get the profile picture of connected user" })
@ApiOkResponse({ description: "The user profile picture" })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Get("me/picture")
async getProfilePicture(@Request() req: any, @Response() res: any) {
return await this.usersService.getProfilePicture(req.user.id, res);
}
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'The user profile picture' })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Post('me/picture')
@ApiOperation({ description: 'Upload a new profile picture' })
@UseInterceptors(FileInterceptor('file'))
@ApiOkResponse({ description: "The user profile picture" })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Post("me/picture")
@ApiOperation({ description: "Upload a new profile picture" })
@UseInterceptors(FileInterceptor("file"))
async postProfilePicture(
@Request() req: any,
@UploadedFile(
new ParseFilePipeBuilder()
.addFileTypeValidator({
fileType: 'jpeg',
fileType: "jpeg",
})
.build({
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
@@ -203,10 +205,10 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully logged in', type: User })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Get('me')
@ApiOperation({ description: 'Get the user info of connected user' })
@ApiOkResponse({ description: "Successfully logged in", type: User })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Get("me")
@ApiOperation({ description: "Get the user info of connected user" })
async getProfile(@Request() req: any): Promise<User> {
const user = await this.usersService.user({ id: req.user.id });
if (!user) throw new InternalServerErrorException();
@@ -215,10 +217,10 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully edited profile', type: User })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Put('me')
@ApiOperation({ description: 'Edit the profile of connected user' })
@ApiOkResponse({ description: "Successfully edited profile", type: User })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Put("me")
@ApiOperation({ description: "Edit the profile of connected user" })
editProfile(
@Request() req: any,
@Body() profile: Partial<Profile>,
@@ -241,20 +243,20 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully deleted', type: User })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Delete('me')
@ApiOperation({ description: 'Delete the profile of connected user' })
@ApiOkResponse({ description: "Successfully deleted", type: User })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Delete("me")
@ApiOperation({ description: "Delete the profile of connected user" })
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')
@ApiOperation({ description: 'Edit the settings of connected user' })
@ApiOkResponse({ description: "Successfully edited settings", type: Setting })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Patch("me/settings")
@ApiOperation({ description: "Edit the settings of connected user" })
udpateSettings(
@Request() req: any,
@Body() settingUserDto: UpdateSettingDto,
@@ -267,10 +269,10 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully edited settings', type: Setting })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Get('me/settings')
@ApiOperation({ description: 'Get the settings of connected user' })
@ApiOkResponse({ description: "Successfully edited settings", type: Setting })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Get("me/settings")
@ApiOperation({ description: "Get the settings of connected user" })
async getSettings(@Request() req: any): Promise<Setting> {
const result = await this.settingsService.getUserSetting({
userId: +req.user.id,
@@ -281,28 +283,31 @@ export class AuthController {
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully added liked song' })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Post('me/likes/:id')
addLikedSong(@Request() req: any, @Param('id') songId: number) {
@ApiOkResponse({ description: "Successfully added liked song" })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Post("me/likes/:id")
addLikedSong(@Request() req: any, @Param("id") songId: number) {
return this.usersService.addLikedSong(+req.user.id, +songId);
}
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully removed liked song' })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Delete('me/likes/:id')
removeLikedSong(@Request() req: any, @Param('id') songId: number) {
@ApiOkResponse({ description: "Successfully removed liked song" })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Delete("me/likes/:id")
removeLikedSong(@Request() req: any, @Param("id") songId: number) {
return this.usersService.removeLikedSong(+req.user.id, +songId);
}
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ description: 'Successfully retrieved liked song' })
@ApiUnauthorizedResponse({ description: 'Invalid token' })
@Get('me/likes')
getLikedSongs(@Request() req: any) {
return this.usersService.getLikedSongs(+req.user.id);
@ApiOkResponse({ description: "Successfully retrieved liked song" })
@ApiUnauthorizedResponse({ description: "Invalid token" })
@Get("me/likes")
getLikedSongs(@Request() req: any, @Query("include") include: string) {
return this.usersService.getLikedSongs(
+req.user.id,
mapInclude(include, req, SongController.includableFields),
);
}
}

View File

@@ -2,17 +2,17 @@ import {
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import { User, Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import * as bcrypt from 'bcryptjs';
import { createHash, randomUUID } from 'crypto';
import { createReadStream, existsSync } from 'fs';
import fetch from 'node-fetch';
} from "@nestjs/common";
import { User, Prisma } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";
import * as bcrypt from "bcryptjs";
import { createHash, randomUUID } from "crypto";
import { createReadStream, existsSync } from "fs";
import fetch from "node-fetch";
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
constructor(private prisma: PrismaService) { }
async user(
userWhereUniqueInput: Prisma.UserWhereUniqueInput,
@@ -53,7 +53,7 @@ export class UsersService {
isGuest: true,
// Not realyl clean but better than a separate table or breaking the api by adding nulls.
email: null,
password: '',
password: "",
},
});
}
@@ -63,7 +63,7 @@ export class UsersService {
data: Prisma.UserUpdateInput;
}): Promise<User> {
const { where, data } = params;
if (typeof data.password === 'string')
if (typeof data.password === "string")
data.password = await bcrypt.hash(data.password, 8);
else if (data.password && data.password.set)
data.password = await bcrypt.hash(data.password.set, 8);
@@ -89,9 +89,9 @@ export class UsersService {
const user = await this.user({ id: userId });
if (!user) throw new InternalServerErrorException();
if (!user.email) throw new NotFoundException();
const hash = createHash('md5')
const hash = createHash("md5")
.update(user.email.trim().toLowerCase())
.digest('hex');
.digest("hex");
const resp = await fetch(
`https://www.gravatar.com/avatar/${hash}.jpg?d=404&s=200`,
);
@@ -105,9 +105,10 @@ export class UsersService {
});
}
async getLikedSongs(userId: number) {
async getLikedSongs(userId: number, include?: Prisma.SongInclude) {
return this.prisma.likedSongs.findMany({
where: { userId: userId },
include: { song: include ? { include } : true },
});
}