Добавлена работа с пользователями - создание, редактирование, удаление.
This commit is contained in:
parent
a99fa47470
commit
c5cb7c2984
816
package-lock.json
generated
816
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -14,7 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.734.0",
|
"@aws-sdk/client-s3": "^3.734.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.734.0",
|
"@aws-sdk/s3-request-presigner": "^3.734.0",
|
||||||
"@prisma/client": "^6.2.1",
|
"@prisma/client": "^6.3.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"@types/multer-s3": "^3.0.3",
|
"@types/multer-s3": "^3.0.3",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/winston": "^2.4.4",
|
"@types/winston": "^2.4.4",
|
||||||
"prisma": "^6.2.1",
|
"prisma": "^6.3.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"displayName" TEXT NOT NULL,
|
||||||
|
"avatarUrl" TEXT NOT NULL,
|
||||||
|
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"permissions" JSONB NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Article" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"excerpt" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"categoryId" INTEGER NOT NULL,
|
||||||
|
"city" TEXT NOT NULL,
|
||||||
|
"coverImage" TEXT NOT NULL,
|
||||||
|
"readTime" INTEGER NOT NULL,
|
||||||
|
"likes" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"dislikes" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"publishedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"authorId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Category" (
|
||||||
|
"id" INTEGER NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "GalleryImage" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"caption" TEXT NOT NULL,
|
||||||
|
"alt" TEXT NOT NULL,
|
||||||
|
"width" INTEGER NOT NULL,
|
||||||
|
"height" INTEGER NOT NULL,
|
||||||
|
"size" INTEGER NOT NULL,
|
||||||
|
"format" TEXT NOT NULL,
|
||||||
|
"articleId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"order" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
CONSTRAINT "GalleryImage_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserReaction" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"articleId" TEXT NOT NULL,
|
||||||
|
"reaction" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "UserReaction_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Category_name_key" ON "Category"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "UserReaction_userId_articleId_key" ON "UserReaction"("userId", "articleId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Article" ADD CONSTRAINT "Article_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Article" ADD CONSTRAINT "Article_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "GalleryImage" ADD CONSTRAINT "GalleryImage_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
@ -12,6 +12,7 @@ model User {
|
|||||||
email String @unique
|
email String @unique
|
||||||
password String
|
password String
|
||||||
displayName String
|
displayName String
|
||||||
|
avatarUrl String
|
||||||
isAdmin Boolean @default(false)
|
isAdmin Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
@ -92,10 +92,10 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
|
|
||||||
export async function updateArticle(req: AuthRequest, res: Response) : Promise<void> {
|
export async function updateArticle(req: AuthRequest, res: Response) : Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { title, excerpt, content, categoryId, city, coverImage, readTime } = req.body;
|
const { title, excerpt, content, category, city, coverImage, readTime } = req.body;
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
res.status(401).json({ error: 'Not authenticated' });
|
res.status(401).json({ error: 'Пользователь не вошел в систему' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,21 +105,12 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!article) {
|
if (!article) {
|
||||||
res.status(404).json({ error: 'Article not found' });
|
res.status(404).json({ error: 'Статья не найдена' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const category = await prisma.category.findUnique({
|
if (!checkPermission(req.user, category, 'edit')) {
|
||||||
where: { id: parseInt(categoryId) }
|
res.status(403).json({ error: 'Нет прав на выполнение этой операции' });
|
||||||
});
|
|
||||||
|
|
||||||
if (!category) {
|
|
||||||
res.status(400).json({ error: 'Invalid category' });
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkPermission(req.user, categoryId, 'edit')) {
|
|
||||||
res.status(403).json({ error: 'Permission denied' });
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +120,7 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
title,
|
title,
|
||||||
excerpt,
|
excerpt,
|
||||||
content,
|
content,
|
||||||
categoryId: parseInt(categoryId),
|
categoryId: parseInt(category),
|
||||||
city,
|
city,
|
||||||
coverImage,
|
coverImage,
|
||||||
readTime
|
readTime
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { prisma } from '../../../lib/prisma';
|
import { prisma } from '../../../lib/prisma';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
import { CategoryMap } from '../../../types';
|
||||||
|
|
||||||
export async function listArticles(req: Request, res: Response) {
|
export async function listArticles(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const { page = 1, category, city } = req.query;
|
const { page = 1, category, city } = req.query;
|
||||||
const perPage = 6;
|
const perPage = 6;
|
||||||
|
|
||||||
|
const categoryId = CategoryMap[category as string];
|
||||||
|
|
||||||
// Проверка и преобразование параметров в строковые значения
|
// Проверка и преобразование параметров в строковые значения
|
||||||
const where: Prisma.ArticleWhereInput = {
|
const where: Prisma.ArticleWhereInput = {
|
||||||
...(category && { category: { name: category as string } }),
|
...(categoryId && { categoryId: categoryId }),
|
||||||
...(city && { city: city as string }),
|
...(city && { city: city as string }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ export async function login(req: Request, res: Response) {
|
|||||||
|
|
||||||
export async function signIn(req: Request, res: Response) {
|
export async function signIn(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const { email, password, displayName } = req.body;
|
const { email, password, displayName, avatarUrl } = req.body;
|
||||||
const user = await authService.createUser({email : email, password : password, displayName : displayName, permissions : {}});
|
const user = await authService.createUser({email : email, password : password, displayName : displayName, avatarUrl : avatarUrl, permissions : {}});
|
||||||
res.json({ user });
|
res.json({ user });
|
||||||
} catch {
|
} catch {
|
||||||
res.status(401).json({ error: 'Invalid signIn credentials' });
|
res.status(401).json({ error: 'Invalid signIn credentials' });
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { AuthRequest } from '../../../middleware/auth';
|
import { AuthRequest } from '../../../middleware/auth';
|
||||||
import { userService } from '../../../services/userService';
|
import { userService } from '../../../services/userService';
|
||||||
|
import { logger } from '../../../config/logger';
|
||||||
|
import bcrypt from 'bcryptjs';
|
||||||
|
import { prisma } from '../../../lib/prisma';
|
||||||
|
import { getDefaultPermissions } from '../../../utils/permissions';
|
||||||
|
|
||||||
|
|
||||||
export async function getUsers(req: AuthRequest, res: Response): Promise<void> {
|
export async function getUsers(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@ -18,6 +23,109 @@ export async function getUsers(req: AuthRequest, res: Response): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createUser(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Non-admin user ${req.user?.id} attempted to create user`);
|
||||||
|
res.status(403).json({ error: 'Admin access required' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { email, password, displayName, avatarUrl } = req.body;
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: { email }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
res.status(400).json({ error: 'User already exists' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
// Create user with default permissions
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
email,
|
||||||
|
password: hashedPassword,
|
||||||
|
displayName,
|
||||||
|
avatarUrl,
|
||||||
|
permissions: getDefaultPermissions()
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
displayName: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
permissions: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`User created successfully: ${user.id}`);
|
||||||
|
res.status(201).json(user);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error creating user:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to create user' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUser(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Non-admin user ${req.user?.id} attempted to update user`);
|
||||||
|
res.status(403).json({ error: 'Admin access required' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
const { email, password, displayName, avatarUrl } = req.body;
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
|
res.status(404).json({ error: 'User not found' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare update data
|
||||||
|
const updateData: any = {
|
||||||
|
email,
|
||||||
|
displayName,
|
||||||
|
avatarUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only update password if provided
|
||||||
|
if (password) {
|
||||||
|
updateData.password = await bcrypt.hash(password, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user
|
||||||
|
const user = await prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data: updateData,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
displayName: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
permissions: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`User updated successfully: ${user.id}`);
|
||||||
|
res.json(user);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error updating user:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to update user' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateUserPermissions(req: AuthRequest, res: Response): Promise<void> {
|
export async function updateUserPermissions(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Проверка прав администратора
|
// Проверка прав администратора
|
||||||
@ -37,3 +145,36 @@ export async function updateUserPermissions(req: AuthRequest, res: Response): Pr
|
|||||||
res.status(500).json({ error: 'Server error' });
|
res.status(500).json({ error: 'Server error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteUser(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Non-admin user ${req.user?.id} attempted to delete user`);
|
||||||
|
res.status(403).json({ error: 'Admin access required' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
|
res.status(404).json({ error: 'User not found' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete user
|
||||||
|
await prisma.user.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`User deleted successfully: ${id}`);
|
||||||
|
res.json({ message: 'User deleted successfully' });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error deleting user:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to delete user' });
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,22 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { auth } from '../../middleware/auth';
|
import { auth } from '../../middleware/auth';
|
||||||
import { getUsers, updateUserPermissions } from './controllers/users';
|
import {
|
||||||
|
getUsers,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
updateUserPermissions,
|
||||||
|
deleteUser
|
||||||
|
} from './controllers/users';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
// All routes require authentication
|
||||||
|
router.use(auth);
|
||||||
|
|
||||||
router.get('/', auth, getUsers);
|
router.get('/', auth, getUsers);
|
||||||
|
router.post('/', createUser);
|
||||||
|
router.put('/:id', updateUser);
|
||||||
router.put('/:id/permissions', auth, updateUserPermissions);
|
router.put('/:id/permissions', auth, updateUserPermissions);
|
||||||
|
router.delete('/:id', deleteUser);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@ -17,6 +17,7 @@ export const authService = {
|
|||||||
password: string;
|
password: string;
|
||||||
id: string;
|
id: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
avatarUrl: string;
|
||||||
permissions: JsonValue;
|
permissions: JsonValue;
|
||||||
} | null = await prisma.user.findUnique({
|
} | null = await prisma.user.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
@ -24,6 +25,7 @@ export const authService = {
|
|||||||
id: true,
|
id: true,
|
||||||
email: true,
|
email: true,
|
||||||
displayName: true,
|
displayName: true,
|
||||||
|
avatarUrl: true,
|
||||||
password: true,
|
password: true,
|
||||||
permissions: true
|
permissions: true
|
||||||
}
|
}
|
||||||
@ -76,6 +78,7 @@ export const authService = {
|
|||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
avatarUrl: string;
|
||||||
permissions: any;
|
permissions: any;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
@ -86,6 +89,7 @@ export const authService = {
|
|||||||
email: string;
|
email: string;
|
||||||
id: string;
|
id: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
avatarUrl: string;
|
||||||
permissions: JsonValue;
|
permissions: JsonValue;
|
||||||
} = await prisma.user.create({
|
} = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
@ -96,6 +100,7 @@ export const authService = {
|
|||||||
id: true,
|
id: true,
|
||||||
email: true,
|
email: true,
|
||||||
displayName: true,
|
displayName: true,
|
||||||
|
avatarUrl: true,
|
||||||
permissions: true
|
permissions: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
||||||
import sharp from 'sharp';
|
|
||||||
import { logger } from '../config/logger';
|
|
||||||
import { imageResolutions } from '../config/imageResolutions';
|
|
||||||
|
|
||||||
const s3Client = new S3Client({
|
|
||||||
region: process.env.AWS_REGION || 'ru-central1',
|
|
||||||
endpoint: process.env.AWS_ENDPOINT || '',
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
|
|
||||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Создание клиента S3
|
|
||||||
export const createS3Client = () => {
|
|
||||||
return new S3Client({
|
|
||||||
region: process.env.AWS_REGION || 'ru-central1',
|
|
||||||
endpoint: process.env.AWS_ENDPOINT || '',
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
|
|
||||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const BUCKET_NAME = process.env.AWS_S3_BUCKET || '';
|
|
||||||
|
|
||||||
export const s3Service = {
|
|
||||||
uploadOriginalFile: async (file: Express.MulterS3.File) => {
|
|
||||||
logger.info(`Оригинальный файл загружен в S3: ${file.key}`);
|
|
||||||
return file.key;
|
|
||||||
},
|
|
||||||
|
|
||||||
optimizeAndUpload: async (fileBuffer: Buffer, originalKey: string, resolutionId: string) => {
|
|
||||||
const selectedResolution = imageResolutions.find(r => r.id === resolutionId);
|
|
||||||
if (!selectedResolution) {
|
|
||||||
throw new Error('Недопустимое разрешение');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Оптимизация изображения
|
|
||||||
const optimizedBuffer = await sharp(fileBuffer)
|
|
||||||
.resize(selectedResolution.width, selectedResolution.height, {
|
|
||||||
fit: 'inside',
|
|
||||||
withoutEnlargement: true,
|
|
||||||
})
|
|
||||||
.webp({ quality: 80 })
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
// Генерация нового ключа для оптимизированного файла
|
|
||||||
const optimizedKey = originalKey.replace(/\.[^/.]+$/, '.webp');
|
|
||||||
|
|
||||||
// Загрузка оптимизированного файла
|
|
||||||
await s3Client.send(
|
|
||||||
new PutObjectCommand({
|
|
||||||
Bucket: BUCKET_NAME,
|
|
||||||
Key: optimizedKey,
|
|
||||||
Body: optimizedBuffer,
|
|
||||||
ContentType: 'image/webp',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(`Оптимизированное изображение загружено в S3: ${optimizedKey}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: optimizedKey,
|
|
||||||
width: selectedResolution.width,
|
|
||||||
height: selectedResolution.height,
|
|
||||||
format: 'webp',
|
|
||||||
size: optimizedBuffer.length,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Ошибка при оптимизации и загрузке файла:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
@ -37,3 +37,14 @@ export interface Category {
|
|||||||
|
|
||||||
export type CategoryName = 'Film' | 'Theater' | 'Music' | 'Sports' | 'Art' | 'Legends' | 'Anniversaries' | 'Memory';
|
export type CategoryName = 'Film' | 'Theater' | 'Music' | 'Sports' | 'Art' | 'Legends' | 'Anniversaries' | 'Memory';
|
||||||
export type City = 'New York' | 'London';
|
export type City = 'New York' | 'London';
|
||||||
|
|
||||||
|
export const CategoryMap: Record<string, number> = {
|
||||||
|
'Film': 1,
|
||||||
|
'Theater': 2,
|
||||||
|
'Music': 3,
|
||||||
|
'Sports': 4,
|
||||||
|
'Art': 5,
|
||||||
|
'Legends': 6,
|
||||||
|
'Anniversaries': 7,
|
||||||
|
'Memory': 8,
|
||||||
|
};
|
||||||
|
@ -14,3 +14,18 @@ export const checkCityAccess = (user: User, city: City): boolean => {
|
|||||||
if (user.permissions.isAdmin) return true;
|
if (user.permissions.isAdmin) return true;
|
||||||
return user.permissions.cities.includes(city);
|
return user.permissions.cities.includes(city);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getDefaultPermissions = () => ({
|
||||||
|
categories: {
|
||||||
|
Film: { create: false, edit: false, delete: false },
|
||||||
|
Theater: { create: false, edit: false, delete: false },
|
||||||
|
Music: { create: false, edit: false, delete: false },
|
||||||
|
Sports: { create: false, edit: false, delete: false },
|
||||||
|
Art: { create: false, edit: false, delete: false },
|
||||||
|
Legends: { create: false, edit: false, delete: false },
|
||||||
|
Anniversaries: { create: false, edit: false, delete: false },
|
||||||
|
Memory: { create: false, edit: false, delete: false }
|
||||||
|
},
|
||||||
|
cities: [],
|
||||||
|
isAdmin: false
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user