Skip to content

Instantly share code, notes, and snippets.

@eadortsu
Created March 10, 2024 22:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eadortsu/a9b715369ace30d2cef1df547911da10 to your computer and use it in GitHub Desktop.
Save eadortsu/a9b715369ace30d2cef1df547911da10 to your computer and use it in GitHub Desktop.
Blog Scaffold
import { ObjectType, Field } from '@nestjs/graphql';
import { Column, CreateDateColumn, DeleteDateColumn, Entity, Index, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm';
import { Comment } from './comment.entity';
import { Like } from './likes.entity';
import { Paginated } from '../../common/pagination';
@ObjectType()
@Entity('blogs')
export class Blog {
@Field(() => String, { description: 'Id' })
@PrimaryGeneratedColumn('uuid')
id: string;
@Field({
nullable: false,
description: 'Title',
})
@Column({ nullable: false })
title: string;
@Field({
nullable: false,
description: 'slug',
})
@Index()
@Column({ nullable: false })
slug: string;
@Field({
nullable: true,
description: 'Image',
})
@Column({ nullable: true })
image: string;
@Field(() => [String], {
nullable: true,
description: 'tags',
})
@Column({ type: 'jsonb', nullable: true, default: [] })
tags: string[];
@Field(() => [String], {
nullable: true,
description: 'internal tags',
})
@Column({ type: 'jsonb', nullable: true, default: [] })
internalTags: string[];
@Field({
nullable: false,
description: 'html content',
})
@Column({ nullable: false })
htmlContent: string;
@Field({
nullable: false,
description: 'content',
})
@Column({ nullable: false })
content: string;
@Field({
nullable: false,
description: 'short description',
})
@Column({ nullable: false })
shortDescription: string;
@Field(() => [String], {
nullable: true,
description: 'credits',
})
@Column({ type: 'jsonb', nullable: true, default: [] })
credits: string[];
@Field(() => [Comment], { description: 'Blog', nullable: 'itemsAndList' })
@OneToMany(() => Comment, (comment) => comment.blog)
comments: Comment[];
@Field(() => [Like], { description: 'Blog', nullable: 'itemsAndList' })
@OneToMany(() => Like, (like) => like.blog)
likes: Like[];
@Field({
nullable: false,
description: 'publish',
})
@Column({ nullable: false, default: true })
publish: boolean;
@Field({
nullable: false,
description: 'status',
})
@Column({ nullable: false, default: true })
status: boolean;
@Field({ nullable: false, description: 'featured' })
@Column({ nullable: false, default: false })
featured: boolean;
@Field({ nullable: false, description: 'time to read' })
@Column({ nullable: false, default: 0 })
timeToRead: number;
@Field()
@Column()
@CreateDateColumn()
createdAt: Date;
@Field()
@Column()
@UpdateDateColumn()
updatedAt: Date;
@Field({ nullable: true })
@Column()
@DeleteDateColumn()
deletedAt: Date;
}
@ObjectType('PaginatedBlog')
export class PaginatedBlog extends Paginated(Blog) {}
import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql';
import { Blog, PaginatedBlog } from './entities/blog.entity';
import { CreateBlogInput } from './dto/create-blog.input';
import { UpdateBlogInput } from './dto/update-blog.input';
import { BlogService } from './blog.service';
import { PaginatedEntityInput } from '../common/pagination';
@Resolver(() => Blog)
export class BlogResolver {
constructor(private readonly blogService: BlogService) {}
@Mutation(() => Blog, { name: 'CreateBlog' })
async createBlog(@Args('createBlogInput') createBlogInput: CreateBlogInput) {
return await this.blogService.create(createBlogInput);
}
@Query(() => PaginatedBlog, { name: 'Blogs' })
async findAll(
@Args('options') options?: PaginatedEntityInput,
): Promise<PaginatedBlog> {
return await this.blogService.findAll(options);
}
@Query(() => Blog, { name: 'Blog' })
async findOne(@Args('id', { type: () => String }) id: string) {
return await this.blogService.findOne(id);
}
@Mutation(() => Blog, { name: 'UpdateBlog' })
async updateBlog(@Args('updateBlogInput') updateBlogInput: UpdateBlogInput) {
return await this.blogService.update(updateBlogInput);
}
@Mutation(() => Blog, { name: 'RemoveBlog' })
async removeBlog(@Args('id', { type: () => String }) id: string) {
return await this.blogService.remove(id);
}
}
import { Injectable } from '@nestjs/common';
import { CreateBlogInput } from './dto/create-blog.input';
import { UpdateBlogInput } from './dto/update-blog.input';
import { InjectRepository } from '@nestjs/typeorm';
import { Blog } from './entities/blog.entity';
import { Repository } from 'typeorm';
import { utils } from '../utils';
import { UploadService } from '../upload/upload.service';
import { JSDOM } from 'jsdom';
import {
calcSkippedItems,
PaginatedEntityInput,
Pagination,
} from '../common/pagination';
@Injectable()
export class BlogService {
constructor(
@InjectRepository(Blog)
private blogRepository: Repository<Blog>,
private uploadService: UploadService,
) {}
async create(createBlogInput: CreateBlogInput) {
const preparedBlogInput = await this.prepareBlogInput(createBlogInput);
return await this.blogRepository.save(preparedBlogInput);
}
async update(updateBlogInput: UpdateBlogInput) {
const preparedBlogInput = await this.prepareBlogInput(updateBlogInput);
await this.blogRepository.save(preparedBlogInput);
return await this.findOne(updateBlogInput.id);
}
async findAll(options: PaginatedEntityInput) {
const {
pagination: { limit, page },
sorting: { column, order } = { column: 'createdAt', order: 'DESC' },
filters = [],
} = options;
const skippedItemsCount = calcSkippedItems(page, limit);
const [results, total] = await this.blogRepository.findAndCount({
where: { ...utils.getFilters(filters) },
skip: skippedItemsCount,
take: limit,
order: { [column]: order },
});
return new Pagination<Blog>({
page,
limit,
results,
total,
});
}
async findOne(id: string) {
return await this.blogRepository.findOneOrFail({
where: { id },
relations: ['cities', 'countries', 'places'],
});
}
async remove(id: string) {
return await this.blogRepository.softDelete({ id });
}
calculateReadTime(content: string): number {
const words = content.split(' ').length;
return Math.ceil(words / 200);
}
async prepareBlogInput(blogPayload: CreateBlogInput | UpdateBlogInput) {
const { userId, cityIds, countryIds, placeIds, imageFile, ...blogInput } =
blogPayload;
blogInput.slug = utils.slugify(blogInput.title);
if (blogInput.htmlContent) {
const dom = new JSDOM();
const parser = new dom.window.DOMParser();
const doc = parser.parseFromString(blogInput.htmlContent, 'text/html');
blogInput.content = doc.body.textContent || '';
}
if (!blogInput.shortDescription) {
blogInput.shortDescription = blogInput.content.slice(0, 300);
}
if (imageFile) {
const image = await imageFile;
blogInput.image = await this.uploadService.uploadToFirebase({
file: image,
path: `blog`,
fileName: blogInput.slug,
});
}
blogInput.timeToRead = this.calculateReadTime(blogInput.content);
return blogInput;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment