Skip to content

Commit

Permalink
[hexlet-rus#537] Implement admin section
Browse files Browse the repository at this point in the history
- The migration file has been added, the isAdmin column has been added to the users table.
- The migration file implements the logic of installing the admin for the email specified in the environment variable ADMIN_EMAIL.
- The admin module has been added.
- Added a controller to get a user template.
- In the user creation method, the logic of installing the admin during registration has been added.

На данном этапе:
- Добавлен файл миграции, добавлена колонка isAdmin в таблицу users.
- В файле миграции реализована логика установки админа для емейла указанного в переменной окружения ADMIN_EMAIL.
- Добавлен admin module.
- Добавлен контроллер для получения шаблона пользователей.
- В методе создания пользователя добавлена логика установки админа при регистрации.
  • Loading branch information
SidorovVladimir committed Jan 11, 2025
1 parent b33fef0 commit 85e73d4
Show file tree
Hide file tree
Showing 13 changed files with 1,276 additions and 692 deletions.
3 changes: 3 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ GITHUB_USER_URL=https://api.github.com/user
SENTRY_DSN=https://[email protected]/000000
FRONTEND_URL=http://localhost:3000
EMAIL_FROM=runit@localhost


ADMIN_EMAIL=[email protected]
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"dotenv": "^16.4.7",
"express": "^4.18.2",
"express-ws": "^5.0.2",
"generate-password": "^1.7.0",
Expand Down
27 changes: 27 additions & 0 deletions backend/src/admins/admins.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-disable no-useless-constructor */

import {
Controller,
DefaultValuePipe,
Get,
ParseIntPipe,
Query,
Render,
} from '@nestjs/common';
import { User } from '../entities/user.entity';
import { AdminsService } from './admins.service';

@Controller('admin')
export class AdminsController {
constructor(private readonly adminsService: AdminsService) {}

@Get('users')
@Render('users.pug')
async findAllUsers(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page = 1,
) {
const take = 10;
const users: User[] = await this.adminsService.findAllUsers(page, take);
return { users, currentPage: page };
}
}
11 changes: 11 additions & 0 deletions backend/src/admins/admins.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { UsersModule } from '../users/users.module';
import { AdminsController } from './admins.controller';
import { AdminsService } from './admins.service';

@Module({
imports: [UsersModule],
providers: [AdminsService],
controllers: [AdminsController],
})
export class AdminsModule {}
14 changes: 14 additions & 0 deletions backend/src/admins/admins.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable no-useless-constructor */

import { Injectable } from '@nestjs/common';
import { User } from '../entities/user.entity';
import { UsersService } from '../users/users.service';

@Injectable()
export class AdminsService {
constructor(private readonly usersService: UsersService) {}

findAllUsers(page: number, take: number): Promise<User[]> {
return this.usersService.findAllUsers(page, take);
}
}
Empty file.
47 changes: 47 additions & 0 deletions backend/src/admins/views/users.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
doctype html
html
head
meta(charset='utf-8')
meta(name='viewport' content='width=device-width, initial-scale=1')
title User List
link(href=' https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css' rel='stylesheet')
body
//- script(src='https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js')
nav.navbar.navbar-expand-lg.bg-body-tertiary
.container-fluid
.collapse.navbar-collapse#navbarSupportedContent
ul.navbar-nav.me-auto.mb-2.mb-lg-0
li.nav-item
a.nav-link(href='#') На главную
li.nav-item
a.nav-link(href='#') Пользователи
li.nav-item
a.nav-link(href='#') Сниппеты
.container
.d-flex.align-items-center.justify-content-center
h1.text-center.font-weight-bold.pb-3
| Пользователи
table.table
thead
tr
th ID
th Имя
th Email
th Админ
th Действия
tbody
each user in users
tr
td= user.id
td= user.username
td= user.email
td= user.isAdmin ? 'Да' : 'Нет'
td
div
a.link-dark.link-offset-2.link-underline-opacity-25.link-underline-opacity-100-hover(href='#') Редактировать
div
a.link-dark.link-offset-2.link-underline-opacity-25.link-underline-opacity-100-hover(href='#') Удалить
if currentPage > 1
a(href=`users?page=${currentPage - 1}`) Previos
if users.length >= 10
a(href=`users?page=${currentPage + 1}`) Next
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import appConfig from './config/app.config';

import '@sentry/tracing';
import { RunnerModule } from './runner/runner.module';
import { AdminsModule } from './admins/admins.module';

@Module({
imports: [
Expand All @@ -44,6 +45,7 @@ import { RunnerModule } from './runner/runner.module';
AuthModule,
RunnerModule,
EventsModule,
AdminsModule,
MailerModule.forRootAsync({
useClass: MailerConfig,
}),
Expand Down
3 changes: 3 additions & 0 deletions backend/src/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class User {
@Column('text')
password: string;

@Column('boolean', { default: false })
isAdmin: boolean;

@OneToMany('Snippet', 'user')
snippets: Snippet[];

Expand Down
3 changes: 3 additions & 0 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable import/no-import-module-exports */
import { join } from 'node:path';
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { json } from 'body-parser';
Expand Down Expand Up @@ -29,6 +30,8 @@ async function bootstrap() {
app.enable('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
app.use(cookieParser());
app.use(json({ limit: '500kb' }));
app.setBaseViewsDir(join(__dirname, 'admins/views'));
app.setViewEngine('pug');
app.useGlobalPipes(new ValidationPipe());

const config = new DocumentBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
import { config } from 'dotenv';

config();
export class AddIsAdminColumnTableUsers1736530329399
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
'users',
new TableColumn({
name: 'isAdmin',
type: 'boolean',
default: false,
}),
);

const adminEmail = process.env.ADMIN_EMAIL;

await queryRunner.query(
`UPDATE users SET isAdmin = true WHERE email = $1`,
[adminEmail],
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('users', 'isAdmin');
}
}
11 changes: 10 additions & 1 deletion backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ export class UsersService {
}

async create(createUserDto: CreateUserDto): Promise<User> {
const adminEmail = process.env.ADMIN_EMAIL;
const user = new User();
user.username = createUserDto.username;
user.email = createUserDto.email.toLowerCase();
user.password = createUserDto.password;
user.isAdmin = createUserDto.email.toLowerCase() === adminEmail;
const newUser = await this.usersRepository.save(user);
const userSettings = await this.userSettingsRepository.create({
userId: newUser.id,
Expand Down Expand Up @@ -181,10 +183,17 @@ export class UsersService {
await this.usersRepository.delete({ id });
}

findAll(): Promise<User[]> {
async findAll(): Promise<User[]> {
return this.usersRepository.find();
}

async findAllUsers(page: number, take: number): Promise<User[]> {
return this.usersRepository.find({
skip: (page - 1) * take,
take,
});
}

async getData({ id }: IUser): Promise<any> {
const currentUser = await this.usersRepository.findOneBy({ id });
const settingsUser = await this.userSettingsRepository.findOne({
Expand Down
Loading

0 comments on commit 85e73d4

Please sign in to comment.