mirror of
https://github.com/sergeyyarkov/educt-server.git
synced 2026-10-11 18:29:15 +03:00
feat: image repository & images table
This commit is contained in:
parent
fb92e0e4e4
commit
18819024b2
@ -17,13 +17,14 @@ import Category from 'App/Models/Category';
|
|||||||
import Lesson from 'App/Models/Lesson';
|
import Lesson from 'App/Models/Lesson';
|
||||||
import User from 'App/Models/User';
|
import User from 'App/Models/User';
|
||||||
import CourseStatusEnum from 'App/Datatypes/Enums/CourseStatusEnum';
|
import CourseStatusEnum from 'App/Datatypes/Enums/CourseStatusEnum';
|
||||||
|
import Image from './Image';
|
||||||
|
|
||||||
export default class Course extends BaseModel {
|
export default class Course extends BaseModel {
|
||||||
@column({ isPrimary: true })
|
@column({ isPrimary: true })
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
@column()
|
@column({ serializeAs: null })
|
||||||
public bg_image: string;
|
public bg_image_id: number;
|
||||||
|
|
||||||
@column()
|
@column()
|
||||||
public title: string;
|
public title: string;
|
||||||
@ -45,6 +46,11 @@ export default class Course extends BaseModel {
|
|||||||
})
|
})
|
||||||
public category: BelongsTo<typeof Category>;
|
public category: BelongsTo<typeof Category>;
|
||||||
|
|
||||||
|
@belongsTo(() => Image, {
|
||||||
|
foreignKey: 'bg_image_id',
|
||||||
|
})
|
||||||
|
public image: BelongsTo<typeof Image>;
|
||||||
|
|
||||||
@belongsTo(() => User, {
|
@belongsTo(() => User, {
|
||||||
foreignKey: 'teacher_id',
|
foreignKey: 'teacher_id',
|
||||||
})
|
})
|
||||||
|
|||||||
22
app/Models/Image.ts
Normal file
22
app/Models/Image.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm';
|
||||||
|
|
||||||
|
export default class Image extends BaseModel {
|
||||||
|
@column({ isPrimary: true })
|
||||||
|
public id: number;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public path: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@column()
|
||||||
|
public ext: string;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true })
|
||||||
|
public createdAt: DateTime;
|
||||||
|
|
||||||
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
||||||
|
public updatedAt: DateTime;
|
||||||
|
}
|
||||||
@ -1,13 +1,3 @@
|
|||||||
/**
|
|
||||||
* Services
|
|
||||||
*/
|
|
||||||
import CloudinaryService from '@ioc:Adonis/Addons/Cloudinary';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helpers
|
|
||||||
*/
|
|
||||||
import { cuid } from '@ioc:Adonis/Core/Helpers';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datatypes
|
* Datatypes
|
||||||
*/
|
*/
|
||||||
@ -160,16 +150,10 @@ export default class CourseRepository {
|
|||||||
* @returns Created course
|
* @returns Created course
|
||||||
*/
|
*/
|
||||||
public async create(data: CreateCourseValidator['schema']['props']): Promise<Course> {
|
public async create(data: CreateCourseValidator['schema']['props']): Promise<Course> {
|
||||||
/**
|
|
||||||
* Load background image
|
|
||||||
*/
|
|
||||||
const imageUploadResponse = await CloudinaryService.upload(data.background_image, cuid());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new course
|
* Create new course
|
||||||
*/
|
*/
|
||||||
const course = await this.Course.create({
|
const course = await this.Course.create({
|
||||||
bg_image: imageUploadResponse.secure_url,
|
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
status: CourseStatusEnum.DRAFT, // make course DRAFT before adding lessons
|
status: CourseStatusEnum.DRAFT, // make course DRAFT before adding lessons
|
||||||
|
|||||||
42
app/Repositories/ImageRepository.ts
Normal file
42
app/Repositories/ImageRepository.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
||||||
|
import CloudinaryService from '@ioc:Adonis/Addons/Cloudinary';
|
||||||
|
import Image from 'App/Models/Image';
|
||||||
|
|
||||||
|
export default class ImageRepository {
|
||||||
|
private Image: typeof Image;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.Image = Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create image in cloudinary cloud
|
||||||
|
*
|
||||||
|
* @param file File
|
||||||
|
* @param publicId Image public id
|
||||||
|
* @returns Created image
|
||||||
|
*/
|
||||||
|
public async createInCloudinaryCloud(file: MultipartFileContract, publicId: string | undefined): Promise<Image> {
|
||||||
|
const response = await CloudinaryService.upload(file, publicId);
|
||||||
|
const image = await this.Image.create({ name: publicId, path: response.secure_url, ext: response.format });
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete image from cloudinary cloud
|
||||||
|
*
|
||||||
|
* @param id Image id
|
||||||
|
* @returns Result or null
|
||||||
|
*/
|
||||||
|
public async deleteFormCloudinaryCloud(id: number): Promise<any> {
|
||||||
|
const image = await this.Image.query().where('id', id).first();
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
const result = await CloudinaryService.destroy(image.name);
|
||||||
|
await image.delete();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import * as cloudinary from 'cloudinary';
|
import * as cloudinary from 'cloudinary';
|
||||||
import { UploadApiResponse, UploadApiOptions, ResponseCallback } from 'cloudinary';
|
import { UploadApiResponse, UploadApiOptions, ResponseCallback, ResourceType, DeliveryType } from 'cloudinary';
|
||||||
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
||||||
import { CloudinaryConfig, CloudinaryInterface } from '@ioc:Adonis/Addons/Cloudinary';
|
import { CloudinaryConfig, CloudinaryInterface } from '@ioc:Adonis/Addons/Cloudinary';
|
||||||
|
|
||||||
@ -39,6 +39,18 @@ export default class CloudinaryService implements CloudinaryInterface {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async destroy(
|
||||||
|
publicId: string,
|
||||||
|
options?: {
|
||||||
|
resource_type?: ResourceType;
|
||||||
|
type?: DeliveryType;
|
||||||
|
invalidate?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<any> {
|
||||||
|
const response = await this.cloudinary.v2.uploader.destroy(publicId, options);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
public getConfig(): CloudinaryConfig {
|
public getConfig(): CloudinaryConfig {
|
||||||
return this.config;
|
return this.config;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { cuid } from '@ioc:Adonis/Core/Helpers';
|
||||||
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
import { inject, Ioc } from '@adonisjs/core/build/standalone';
|
||||||
import { AuthContract } from '@ioc:Adonis/Addons/Auth';
|
import { AuthContract } from '@ioc:Adonis/Addons/Auth';
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ import RoleHelper from 'App/Helpers/RoleHelper';
|
|||||||
import CourseRepository from 'App/Repositories/CourseRepository';
|
import CourseRepository from 'App/Repositories/CourseRepository';
|
||||||
import UserRepository from 'App/Repositories/UserRepository';
|
import UserRepository from 'App/Repositories/UserRepository';
|
||||||
import CategoryRepository from 'App/Repositories/CategoryRepository';
|
import CategoryRepository from 'App/Repositories/CategoryRepository';
|
||||||
|
import ImageRepository from 'App/Repositories/ImageRepository';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validators
|
* Validators
|
||||||
@ -31,14 +33,18 @@ export default class CourseService {
|
|||||||
|
|
||||||
private categoryRepository: CategoryRepository;
|
private categoryRepository: CategoryRepository;
|
||||||
|
|
||||||
|
private imageRepository: ImageRepository;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
courseRepository: CourseRepository,
|
courseRepository: CourseRepository,
|
||||||
userRepository: UserRepository,
|
userRepository: UserRepository,
|
||||||
categoryRepository: CategoryRepository
|
categoryRepository: CategoryRepository,
|
||||||
|
imageRepository: ImageRepository
|
||||||
) {
|
) {
|
||||||
this.courseRepository = courseRepository;
|
this.courseRepository = courseRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.categoryRepository = categoryRepository;
|
this.categoryRepository = categoryRepository;
|
||||||
|
this.imageRepository = imageRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -274,11 +280,15 @@ export default class CourseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* Create course */
|
||||||
* Creating new course
|
|
||||||
*/
|
|
||||||
const course = await this.courseRepository.create(data);
|
const course = await this.courseRepository.create(data);
|
||||||
|
|
||||||
|
/* Create image if provided */
|
||||||
|
if (data.background_image) {
|
||||||
|
const bgImage = await this.imageRepository.createInCloudinaryCloud(data.background_image, cuid());
|
||||||
|
await course.related('image').associate(bgImage);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: StatusCodeEnum.CREATED,
|
status: StatusCodeEnum.CREATED,
|
||||||
@ -308,6 +318,11 @@ export default class CourseService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete background image from course
|
||||||
|
*/
|
||||||
|
await this.imageRepository.deleteFormCloudinaryCloud(course.bg_image_id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: StatusCodeEnum.OK,
|
status: StatusCodeEnum.OK,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export default class CreateCourseValidator {
|
|||||||
public schema = schema.create({
|
public schema = schema.create({
|
||||||
title: schema.string(),
|
title: schema.string(),
|
||||||
description: schema.string(),
|
description: schema.string(),
|
||||||
background_image: schema.file({
|
background_image: schema.file.optional({
|
||||||
size: '2mb',
|
size: '2mb',
|
||||||
extnames: ['jpg', 'jpeg', 'png'],
|
extnames: ['jpg', 'jpeg', 'png'],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
declare module '@ioc:Adonis/Addons/Cloudinary' {
|
declare module '@ioc:Adonis/Addons/Cloudinary' {
|
||||||
import { ResponseCallback, TransformationOptions, UploadApiOptions, UploadApiResponse } from 'cloudinary';
|
import {
|
||||||
|
ResponseCallback,
|
||||||
|
TransformationOptions,
|
||||||
|
UploadApiOptions,
|
||||||
|
UploadApiResponse,
|
||||||
|
ResourceType,
|
||||||
|
DeliveryType,
|
||||||
|
} from 'cloudinary';
|
||||||
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
import { MultipartFileContract } from '@ioc:Adonis/Core/BodyParser';
|
||||||
|
|
||||||
export interface CloudinaryConfig {
|
export interface CloudinaryConfig {
|
||||||
@ -18,6 +25,15 @@ declare module '@ioc:Adonis/Addons/Cloudinary' {
|
|||||||
callback?: ResponseCallback
|
callback?: ResponseCallback
|
||||||
): Promise<UploadApiResponse>;
|
): Promise<UploadApiResponse>;
|
||||||
|
|
||||||
|
destroy(
|
||||||
|
publicId: string,
|
||||||
|
options?: {
|
||||||
|
resource_type?: ResourceType;
|
||||||
|
type?: DeliveryType;
|
||||||
|
invalidate?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<any>;
|
||||||
|
|
||||||
getConfig(): CloudinaryConfig;
|
getConfig(): CloudinaryConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export default class Courses extends BaseSchema {
|
|||||||
public async up() {
|
public async up() {
|
||||||
this.schema.createTable(this.tableName, table => {
|
this.schema.createTable(this.tableName, table => {
|
||||||
table.string('id', 21).primary();
|
table.string('id', 21).primary();
|
||||||
table.string('bg_image');
|
table.string('bg_image_id');
|
||||||
table.string('title').notNullable();
|
table.string('title').notNullable();
|
||||||
table.string('description').notNullable();
|
table.string('description').notNullable();
|
||||||
table.string('teacher_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
table.string('teacher_id', 21).unsigned().references('users.id').onDelete('CASCADE');
|
||||||
|
|||||||
20
database/migrations/1629037559962_images.ts
Normal file
20
database/migrations/1629037559962_images.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import BaseSchema from '@ioc:Adonis/Lucid/Schema';
|
||||||
|
|
||||||
|
export default class Images extends BaseSchema {
|
||||||
|
protected tableName = 'images';
|
||||||
|
|
||||||
|
public async up() {
|
||||||
|
this.schema.createTable(this.tableName, table => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('path').notNullable();
|
||||||
|
table.string('name').notNullable();
|
||||||
|
table.string('ext').notNullable();
|
||||||
|
table.timestamp('created_at', { useTz: true });
|
||||||
|
table.timestamp('updated_at', { useTz: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down() {
|
||||||
|
this.schema.dropTable(this.tableName);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user