Skip to content

Commit 65c2cf8

Browse files
committed
feat(backend): add management functionality
1 parent aae0892 commit 65c2cf8

File tree

5 files changed

+189
-0
lines changed

5 files changed

+189
-0
lines changed

backend/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { MailModule } from './mail/mail.module';
2020
import { GitHubModule } from './github/github.module';
2121
import { AppConfigService } from './config/config.service';
2222
import { getDatabaseConfig } from './database.config';
23+
import { DashboardModule } from './dashboard/dashboard.module';
2324

2425
@Module({
2526
imports: [
@@ -56,6 +57,7 @@ import { getDatabaseConfig } from './database.config';
5657
MailModule,
5758
TypeOrmModule.forFeature([User]),
5859
GitHubModule,
60+
DashboardModule,
5961
],
6062
providers: [
6163
AppResolver,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Module } from '@nestjs/common';
2+
import { DashboardResolver } from './dashboard.resolver';
3+
import { DashboardService } from './dashboard.service';
4+
import { TypeOrmModule } from '@nestjs/typeorm';
5+
import { User } from '../user/user.model';
6+
import { UserModule } from '../user/user.module';
7+
8+
@Module({
9+
imports: [TypeOrmModule.forFeature([User]), UserModule],
10+
providers: [DashboardResolver, DashboardService],
11+
exports: [DashboardService],
12+
})
13+
export class DashboardModule {}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
2+
import { DashboardService } from './dashboard.service';
3+
import { User } from '../user/user.model';
4+
import {
5+
CreateUserInput,
6+
UpdateUserInput,
7+
UserFilterInput,
8+
} from './dto/user-input';
9+
import { RequireRoles } from '../decorator/auth.decorator';
10+
import { UseGuards } from '@nestjs/common';
11+
import { GqlAuthGuard } from '../guard/gql-auth.guard';
12+
13+
@Resolver(() => User)
14+
@UseGuards(GqlAuthGuard)
15+
export class DashboardResolver {
16+
constructor(private readonly dashboardService: DashboardService) {}
17+
18+
@Query(() => [User])
19+
@RequireRoles('Admin')
20+
async dashboardUsers(
21+
@Args('filter', { nullable: true }) filter?: UserFilterInput,
22+
): Promise<User[]> {
23+
return this.dashboardService.findUsers(filter);
24+
}
25+
26+
@Query(() => User)
27+
@RequireRoles('Admin')
28+
async dashboardUser(@Args('id') id: string): Promise<User> {
29+
return this.dashboardService.findUserById(id);
30+
}
31+
32+
@Mutation(() => User)
33+
@RequireRoles('Admin')
34+
async createDashboardUser(
35+
@Args('input') input: CreateUserInput,
36+
): Promise<User> {
37+
return this.dashboardService.createUser(input);
38+
}
39+
40+
@Mutation(() => User)
41+
@RequireRoles('Admin')
42+
async updateDashboardUser(
43+
@Args('id') id: string,
44+
@Args('input') input: UpdateUserInput,
45+
): Promise<User> {
46+
return this.dashboardService.updateUser(id, input);
47+
}
48+
49+
@Mutation(() => Boolean)
50+
@RequireRoles('Admin')
51+
async deleteDashboardUser(@Args('id') id: string): Promise<boolean> {
52+
return this.dashboardService.deleteUser(id);
53+
}
54+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Injectable, NotFoundException } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Repository } from 'typeorm';
4+
import { User } from '../user/user.model';
5+
import {
6+
CreateUserInput,
7+
UpdateUserInput,
8+
UserFilterInput,
9+
} from './dto/user-input';
10+
import { hash } from 'bcrypt';
11+
12+
@Injectable()
13+
export class DashboardService {
14+
constructor(
15+
@InjectRepository(User)
16+
private readonly userRepository: Repository<User>,
17+
) {}
18+
19+
async findUsers(filter?: UserFilterInput): Promise<User[]> {
20+
const query = this.userRepository.createQueryBuilder('user');
21+
22+
if (filter?.search) {
23+
query.where('(user.username LIKE :search OR user.email LIKE :search)', {
24+
search: `%${filter.search}%`,
25+
});
26+
}
27+
28+
if (filter?.isActive !== undefined) {
29+
query.andWhere('user.isActive = :isActive', {
30+
isActive: filter.isActive,
31+
});
32+
}
33+
34+
return query.getMany();
35+
}
36+
37+
async findUserById(id: string): Promise<User> {
38+
const user = await this.userRepository.findOne({ where: { id } });
39+
if (!user) {
40+
throw new NotFoundException(`User with ID ${id} not found`);
41+
}
42+
return user;
43+
}
44+
45+
async createUser(input: CreateUserInput): Promise<User> {
46+
const hashedPassword = await hash(input.password, 10);
47+
const user = this.userRepository.create({
48+
...input,
49+
password: hashedPassword,
50+
});
51+
return this.userRepository.save(user);
52+
}
53+
54+
async updateUser(id: string, input: UpdateUserInput): Promise<User> {
55+
const user = await this.findUserById(id);
56+
57+
if (input.password) {
58+
input.password = await hash(input.password, 10);
59+
}
60+
61+
Object.assign(user, input);
62+
return this.userRepository.save(user);
63+
}
64+
65+
async deleteUser(id: string): Promise<boolean> {
66+
const result = await this.userRepository.delete(id);
67+
return result.affected > 0;
68+
}
69+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Field, InputType } from '@nestjs/graphql';
2+
import { IsEmail, IsOptional, IsString, MinLength } from 'class-validator';
3+
4+
@InputType()
5+
export class CreateUserInput {
6+
@Field()
7+
@IsString()
8+
@MinLength(2)
9+
username: string;
10+
11+
@Field()
12+
@IsEmail()
13+
email: string;
14+
15+
@Field()
16+
@IsString()
17+
@MinLength(6)
18+
password: string;
19+
}
20+
21+
@InputType()
22+
export class UpdateUserInput {
23+
@Field({ nullable: true })
24+
@IsString()
25+
@MinLength(2)
26+
@IsOptional()
27+
username?: string;
28+
29+
@Field({ nullable: true })
30+
@IsEmail()
31+
@IsOptional()
32+
email?: string;
33+
34+
@Field({ nullable: true })
35+
@IsString()
36+
@MinLength(6)
37+
@IsOptional()
38+
password?: string;
39+
}
40+
41+
@InputType()
42+
export class UserFilterInput {
43+
@Field({ nullable: true })
44+
@IsString()
45+
@IsOptional()
46+
search?: string;
47+
48+
@Field(() => Boolean, { nullable: true })
49+
@IsOptional()
50+
isActive?: boolean;
51+
}

0 commit comments

Comments
 (0)