Skip to content

Commit

Permalink
feat: Amazing picture, Default avatar #122 #173
Browse files Browse the repository at this point in the history
  • Loading branch information
seheon99 committed Dec 10, 2021
1 parent 74b7e40 commit 6490193
Show file tree
Hide file tree
Showing 14 changed files with 1,185 additions and 60 deletions.
6 changes: 3 additions & 3 deletions backend/src/amazing_picture/amazing_picture.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { UsersModule } from 'src/users/users.module';
import { DatabaseModule } from 'src/database/database.module';
import { StorageModule } from 'src/storage/storage.module';
import { AmazingPictureResolver } from './amazing_picture.resolver';
import { AmazingPictureService } from './amazing_picture.service';

@Module({
imports: [HttpModule, UsersModule],
imports: [StorageModule, DatabaseModule],
providers: [AmazingPictureResolver, AmazingPictureService],
})
export class AmazingPictureModule {}
34 changes: 14 additions & 20 deletions backend/src/amazing_picture/amazing_picture.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import { UnauthorizedException } from '@nestjs/common';
import { UseGuards } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { UserID } from 'src/users/decorators/user-id.decorator';
import { FileUpload, GraphQLUpload } from 'graphql-upload';
import { SiteRoles } from 'src/users/decorators/site-roles.decorator';
import { SiteRoleGuard } from 'src/users/guards/site-role.guard';
import { UserRole } from 'src/users/models/user.model';
import { UsersService } from 'src/users/users.service';
import { env } from 'src/utils/envs';
import { AmazingPictureService } from './amazing_picture.service';

@Resolver()
export class AmazingPictureResolver {
constructor(
private readonly amazingPictureService: AmazingPictureService,
private readonly usersService: UsersService,
) {}
constructor(private readonly amazingPictureService: AmazingPictureService) {}

@Query((returns) => String)
amazingPicture() {
return `http://${env.storage.host}:${env.storage.port}/storage/amazing_picture`;
async amazingPicture() {
return await this.amazingPictureService.find();
}

@Mutation((returns) => Boolean)
async setAmazingPicture(
@UserID() user_id: string,
@Args('amazing_picture') amazing_picture: string,
@UseGuards(SiteRoleGuard)
@SiteRoles(UserRole.ADMIN)
async updateAmazingPicture(
@Args('file', { type: () => GraphQLUpload }) file: FileUpload,
): Promise<boolean> {
// if ((await this.usersService.getSiteRole(user_id)) === UserRole.USER)
// throw new UnauthorizedException(
// 'The user does not have permission to change the amazing picture.',
// );
await this.amazingPictureService.deleteAmazingPicture();
return await this.amazingPictureService.uploadAmazingPicture(
amazing_picture,
return (
(await this.amazingPictureService.delete()) &&
(await this.amazingPictureService.create(file))
);
}
}
57 changes: 32 additions & 25 deletions backend/src/amazing_picture/amazing_picture.service.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
import { HttpService } from '@nestjs/axios';
import { Injectable } from '@nestjs/common';
import { AxiosResponse } from 'axios';
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { FileUpload } from 'graphql-upload';
import { DatabaseService } from 'src/database/database.service';
import { StorageService } from 'src/storage/storage.service';
import { env } from 'src/utils/envs';

@Injectable()
export class AmazingPictureService {
constructor(private readonly httpService: HttpService) {}
constructor(
private readonly storageService: StorageService,
private readonly databaseService: DatabaseService,
) {}

uploadAmazingPicture(amazing_picture: string): Promise<boolean> {
return new Promise((resolve, reject) => {
this.httpService
.post(
`http://${env.storage.host}:${env.storage.port}/upload/amazing_picture`,
amazing_picture,
)
.subscribe({
next: async (axiosResponse: AxiosResponse) => {
if (axiosResponse.status === 201) resolve(true);
else resolve(false);
},
error(error) {
reject(error);
},
complete() {},
});
});
async find() {
return (
await this.databaseService.executeQuery(
`SELECT url FROM ${env.database.schema}.storage_url WHERE filename = "amazing_picture";`,
)
).at(0)?.url;
}

deleteAmazingPicture(): void {
this.httpService.delete(
`http://${env.storage.host}:${env.storage.port}/delete/amazing_picture`,
async create(file: FileUpload) {
const amazingUrl = await this.storageService.post(file);
if (!amazingUrl)
throw new InternalServerErrorException(
`Error occured during upload file, ${file.filename}`,
);
await this.databaseService.executeQuery(
`INSERT INTO ${env.database.schema}.storage_url(filename, url) VALUES("amazing_picture", "${amazingUrl}");`,
);
return true;
}

async delete() {
const filename = (
await this.databaseService.executeQuery(
`DELETE FROM ${env.database.schema}.storage_url WHERE filename = "amazing_picture" RETURNING "url";`,
)
).at(0)?.url;
await this.storageService.delete(filename);
return true;
}
}
4 changes: 2 additions & 2 deletions backend/src/channels/channels.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { UsersService } from 'src/users/users.service';
import { ChannelsService } from './channels.service';
import { Channel, ChannelNotify } from './models/channel.model';
import { PubSub } from 'graphql-subscriptions';
import { ChannelRoleGuard } from './guard/channel-role.guard';
import { ChannelRoleGuard } from '../users/guards/channel-role.guard';
import { UserID } from 'src/users/decorators/user-id.decorator';
import { ChannelRoles } from './decorators/channel-roles.decorator';
import { ChannelRoles } from '../users/decorators/channel-roles.decorator';

@Resolver((of) => Channel)
@UseGuards(ChannelRoleGuard)
Expand Down
1 change: 1 addition & 0 deletions backend/src/storage/storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class StorageService {
})
.pipe(map((res) => res.data)),
);
if (!storageUrl) throw `Error occured during upload file, ${file.filename}`;
this.logger.verbose(`Upload ${file.filename} on storage, ${storageUrl}`);
return storageUrl;
}
Expand Down
6 changes: 6 additions & 0 deletions backend/src/users/decorators/site-roles.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SetMetadata } from '@nestjs/common';
import { UserRole } from 'src/users/models/user.model';

export const SITE_ROLES_KEY = 'siteRoles';
export const SiteRoles = (...roles: UserRole[]) =>
SetMetadata(SITE_ROLES_KEY, roles);
49 changes: 49 additions & 0 deletions backend/src/users/guards/site-role.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql';
import { SITE_ROLES_KEY } from '../decorators/site-roles.decorator';
import { UserRole } from '../models/user.model';
import { UsersService } from '../users.service';

@Injectable()
export class SiteRoleGuard implements CanActivate {
constructor(
private readonly usersService: UsersService,
private readonly reflector: Reflector,
) {}

async canActivate(
context: ExecutionContext & { req: any },
): Promise<boolean> {
const requiredRoles: UserRole[] = this.reflector.get<UserRole[]>(
SITE_ROLES_KEY,
context.getHandler(),
);

if (!requiredRoles) return true;

let session: { uid: string };
if (context.getType() === 'http') {
session = context.switchToHttp().getRequest().session;
} else if (context.getType<GqlContextType>() === 'graphql') {
session = GqlExecutionContext.create(context).getContext().req.session;
} else {
throw `Unexpected context type: ${context.getType()}`;
}

const userSiteRole: UserRole = await this.usersService.getSiteRole(
session.uid,
);

for (const requiredRole of requiredRoles) {
if (requiredRole === UserRole.OWNER && userSiteRole !== UserRole.OWNER)
return false;
else if (
requiredRole === UserRole.ADMIN &&
userSiteRole === UserRole.USER
)
return false;
}
return true;
}
}
20 changes: 16 additions & 4 deletions backend/src/users/users.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
forwardRef,
Inject,
InternalServerErrorException,
UseGuards,
} from '@nestjs/common';
import {
Query,
Expand All @@ -27,6 +28,8 @@ import { Match } from 'src/games/models/match.model';
import { GamesService } from 'src/games/games.service';
import { AchievementsService } from 'src/acheivements/achievements.service';
import { FileUpload, GraphQLUpload } from 'graphql-upload';
import { SiteRoleGuard } from './guards/site-role.guard';
import { SiteRoles } from './decorators/site-roles.decorator';

@Resolver((of) => User)
export class UsersResolver {
Expand Down Expand Up @@ -89,6 +92,13 @@ export class UsersResolver {
);
}

@Mutation((returns) => Boolean)
@UseGuards(SiteRoleGuard)
@SiteRoles(UserRole.ADMIN)
async updateDefaultAvatar(
@Args('file', { type: () => GraphQLUpload }) file: FileUpload,
) {}

@Mutation((returns) => ID)
async createDummyUser(): Promise<string> {
while (true) {
Expand Down Expand Up @@ -202,6 +212,12 @@ export class UsersResolver {
** ANCHOR: ResolveField
*/

@ResolveField('avatar', (returns) => String, { nullable: true })
async getAvatar(@Parent() user: User): Promise<string> {
const { id } = user;
return await this.usersService.getAvatar(id);
}

@ResolveField('friends', (returns) => [User])
async getFriends(@Parent() user: User): Promise<User[]> {
const { id } = user;
Expand Down Expand Up @@ -257,10 +273,6 @@ export class UsersResolver {
return await this.usersService.getGameByUserId(id);
}

/*
** ANCHOR: User Subscription
*/

@Subscription((returns) => UserStatus)
statusChange(@Args('user_id', { type: () => ID }) user_id: string) {
return this.pubSub.asyncIterator(`status_of_${user_id}`);
Expand Down
45 changes: 39 additions & 6 deletions backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export class UsersService {
SELECT
id,
nickname,
avatar,
status_message,
rank_score,
site_role,
Expand All @@ -56,7 +55,6 @@ export class UsersService {
SELECT
id,
nickname,
avatar,
status_message,
rank_score,
site_role,
Expand Down Expand Up @@ -127,7 +125,6 @@ export class UsersService {
SELECT
id,
nickname,
avatar,
status_message,
rank_score,
site_role,
Expand Down Expand Up @@ -218,6 +215,35 @@ export class UsersService {
else return false;
}

async findDefaultAvatar(): Promise<string> {
console.log('Default avatar');
return (
await this.databaseService.executeQuery(
`SELECT url FROM ${env.database.schema}.storage_url WHERE filename = 'default_avatar';`,
)
).at(0)?.url;
}

async createDefaultAvatar(file: FileUpload) {
const filename = await this.storageService.post(file);
await this.databaseService.executeQuery(
`INSERT ${env.database.schema}.storage_url VALUES('default_avatar', '${filename}');`,
);
return true;
}

async deleteDefaultAvatar() {
const filename = (
await this.databaseService.executeQuery(
`SELECT url FROM ${env.database.schema}.storage_url WHERE filename = 'default_avatar';`,
)
).at(0)?.url;
if (!filename) return true;

await this.storageService.delete(filename);
return true;
}

async addFriend(user_id: string, friend_id: string): Promise<boolean> {
if (user_id === friend_id)
throw new BadRequestException('One cannot be their own friend');
Expand Down Expand Up @@ -260,7 +286,6 @@ export class UsersService {
SELECT
u.id,
nickname,
avatar,
status_message,
rank_score,
site_role
Expand Down Expand Up @@ -325,12 +350,21 @@ export class UsersService {
** ANCHOR: ResolveField
*/

async getAvatar(id: string): Promise<string> {
const avatar = (
await this.databaseService.executeQuery(
`SELECT avatar FROM ${env.database.schema}.user WHERE id = ${+id};`,
)
).at(0).avatar;
if (avatar) return avatar;
else return await this.findDefaultAvatar();
}

async getFriend(id: string): Promise<User[]> {
return await this.databaseService.executeQuery(`
SELECT
id,
nickname,
avatar,
status_message,
rank_score,
site_role
Expand All @@ -356,7 +390,6 @@ export class UsersService {
SELECT
u.id,
u.nickname,
u.avatar,
u.status_message,
u.rank_score,
u.site_role,
Expand Down
Loading

0 comments on commit 6490193

Please sign in to comment.