initial commit
Change-Id: Ifa2442bb2e786af351ef5f575ca69828870c8b6e
This commit is contained in:
652
src/app/routes/article/article.service.ts
Normal file
652
src/app/routes/article/article.service.ts
Normal file
@@ -0,0 +1,652 @@
|
||||
import slugify from 'slugify';
|
||||
import prisma from '../../../prisma/prisma-client';
|
||||
import HttpException from '../../models/http-exception.model';
|
||||
import profileMapper from '../profile/profile.utils';
|
||||
import articleMapper from './article.mapper';
|
||||
import { Tag } from '../tag/tag.model';
|
||||
|
||||
const buildFindAllQuery = (query: any, id: number | undefined) => {
|
||||
const queries: any = [];
|
||||
const orAuthorQuery = [];
|
||||
const andAuthorQuery = [];
|
||||
|
||||
orAuthorQuery.push({
|
||||
demo: {
|
||||
equals: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (id) {
|
||||
orAuthorQuery.push({
|
||||
id: {
|
||||
equals: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if ('author' in query) {
|
||||
andAuthorQuery.push({
|
||||
username: {
|
||||
equals: query.author,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const authorQuery = {
|
||||
author: {
|
||||
OR: orAuthorQuery,
|
||||
AND: andAuthorQuery,
|
||||
},
|
||||
};
|
||||
|
||||
queries.push(authorQuery);
|
||||
|
||||
if ('tag' in query) {
|
||||
queries.push({
|
||||
tagList: {
|
||||
some: {
|
||||
name: query.tag,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if ('favorited' in query) {
|
||||
queries.push({
|
||||
favoritedBy: {
|
||||
some: {
|
||||
username: {
|
||||
equals: query.favorited,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return queries;
|
||||
};
|
||||
|
||||
export const getArticles = async (query: any, id?: number) => {
|
||||
const andQueries = buildFindAllQuery(query, id);
|
||||
const articlesCount = await prisma.article.count({
|
||||
where: {
|
||||
AND: andQueries,
|
||||
},
|
||||
});
|
||||
|
||||
const articles = await prisma.article.findMany({
|
||||
where: { AND: andQueries },
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
skip: Number(query.offset) || 0,
|
||||
take: Number(query.limit) || 10,
|
||||
include: {
|
||||
tagList: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
favoritedBy: true,
|
||||
_count: {
|
||||
select: {
|
||||
favoritedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
articles: articles.map((article: any) => articleMapper(article, id)),
|
||||
articlesCount,
|
||||
};
|
||||
};
|
||||
|
||||
export const getFeed = async (offset: number, limit: number, id: number) => {
|
||||
const articlesCount = await prisma.article.count({
|
||||
where: {
|
||||
author: {
|
||||
followedBy: { some: { id: id } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const articles = await prisma.article.findMany({
|
||||
where: {
|
||||
author: {
|
||||
followedBy: { some: { id: id } },
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
skip: offset || 0,
|
||||
take: limit || 10,
|
||||
include: {
|
||||
tagList: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
favoritedBy: true,
|
||||
_count: {
|
||||
select: {
|
||||
favoritedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
articles: articles.map((article: any) => articleMapper(article, id)),
|
||||
articlesCount,
|
||||
};
|
||||
};
|
||||
|
||||
export const createArticle = async (article: any, id: number) => {
|
||||
const { title, description, body, tagList } = article;
|
||||
const tags = Array.isArray(tagList) ? tagList : [];
|
||||
|
||||
if (!title) {
|
||||
throw new HttpException(422, { errors: { title: ["can't be blank"] } });
|
||||
}
|
||||
|
||||
if (!description) {
|
||||
throw new HttpException(422, { errors: { description: ["can't be blank"] } });
|
||||
}
|
||||
|
||||
if (!body) {
|
||||
throw new HttpException(422, { errors: { body: ["can't be blank"] } });
|
||||
}
|
||||
|
||||
const slug = `${slugify(title)}-${id}`;
|
||||
|
||||
const existingTitle = await prisma.article.findUnique({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
select: {
|
||||
slug: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingTitle) {
|
||||
throw new HttpException(422, { errors: { title: ['must be unique'] } });
|
||||
}
|
||||
|
||||
const {
|
||||
authorId,
|
||||
id: articleId,
|
||||
...createdArticle
|
||||
} = await prisma.article.create({
|
||||
data: {
|
||||
title,
|
||||
description,
|
||||
body,
|
||||
slug,
|
||||
tagList: {
|
||||
connectOrCreate: tags.map((tag: string) => ({
|
||||
create: { name: tag },
|
||||
where: { name: tag },
|
||||
})),
|
||||
},
|
||||
author: {
|
||||
connect: {
|
||||
id: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tagList: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
favoritedBy: true,
|
||||
_count: {
|
||||
select: {
|
||||
favoritedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return articleMapper(createdArticle, id);
|
||||
};
|
||||
|
||||
export const getArticle = async (slug: string, id?: number) => {
|
||||
const article = await prisma.article.findUnique({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
include: {
|
||||
tagList: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
favoritedBy: true,
|
||||
_count: {
|
||||
select: {
|
||||
favoritedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!article) {
|
||||
throw new HttpException(404, { errors: { article: ['not found'] } });
|
||||
}
|
||||
|
||||
return articleMapper(article, id);
|
||||
};
|
||||
|
||||
const disconnectArticlesTags = async (slug: string) => {
|
||||
await prisma.article.update({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
data: {
|
||||
tagList: {
|
||||
set: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const updateArticle = async (article: any, slug: string, id: number) => {
|
||||
let newSlug = null;
|
||||
|
||||
const existingArticle = await await prisma.article.findFirst({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
select: {
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingArticle) {
|
||||
throw new HttpException(404, {});
|
||||
}
|
||||
|
||||
if (existingArticle.author.id !== id) {
|
||||
throw new HttpException(403, {
|
||||
message: 'You are not authorized to update this article',
|
||||
});
|
||||
}
|
||||
|
||||
if (article.title) {
|
||||
newSlug = `${slugify(article.title)}-${id}`;
|
||||
|
||||
if (newSlug !== slug) {
|
||||
const existingTitle = await prisma.article.findFirst({
|
||||
where: {
|
||||
slug: newSlug,
|
||||
},
|
||||
select: {
|
||||
slug: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingTitle) {
|
||||
throw new HttpException(422, { errors: { title: ['must be unique'] } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tagList =
|
||||
Array.isArray(article.tagList) && article.tagList?.length
|
||||
? article.tagList.map((tag: string) => ({
|
||||
create: { name: tag },
|
||||
where: { name: tag },
|
||||
}))
|
||||
: [];
|
||||
|
||||
await disconnectArticlesTags(slug);
|
||||
|
||||
const updatedArticle = await prisma.article.update({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
data: {
|
||||
...(article.title ? { title: article.title } : {}),
|
||||
...(article.body ? { body: article.body } : {}),
|
||||
...(article.description ? { description: article.description } : {}),
|
||||
...(newSlug ? { slug: newSlug } : {}),
|
||||
updatedAt: new Date(),
|
||||
tagList: {
|
||||
connectOrCreate: tagList,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tagList: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
favoritedBy: true,
|
||||
_count: {
|
||||
select: {
|
||||
favoritedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return articleMapper(updatedArticle, id);
|
||||
};
|
||||
|
||||
export const deleteArticle = async (slug: string, id: number) => {
|
||||
const existingArticle = await await prisma.article.findFirst({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
select: {
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingArticle) {
|
||||
throw new HttpException(404, {});
|
||||
}
|
||||
|
||||
if (existingArticle.author.id !== id) {
|
||||
throw new HttpException(403, {
|
||||
message: 'You are not authorized to delete this article',
|
||||
});
|
||||
}
|
||||
await prisma.article.delete({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getCommentsByArticle = async (slug: string, id?: number) => {
|
||||
const queries = [];
|
||||
|
||||
queries.push({
|
||||
author: {
|
||||
demo: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (id) {
|
||||
queries.push({
|
||||
author: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const comments = await prisma.article.findUnique({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
include: {
|
||||
comments: {
|
||||
where: {
|
||||
OR: queries,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
body: true,
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = comments?.comments.map((comment: any) => ({
|
||||
...comment,
|
||||
author: {
|
||||
username: comment.author.username,
|
||||
bio: comment.author.bio,
|
||||
image: comment.author.image,
|
||||
following: comment.author.followedBy.some((follow: any) => follow.id === id),
|
||||
},
|
||||
}));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const addComment = async (body: string, slug: string, id: number) => {
|
||||
if (!body) {
|
||||
throw new HttpException(422, { errors: { body: ["can't be blank"] } });
|
||||
}
|
||||
|
||||
const article = await prisma.article.findUnique({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
const comment = await prisma.comment.create({
|
||||
data: {
|
||||
body,
|
||||
article: {
|
||||
connect: {
|
||||
id: article?.id,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
connect: {
|
||||
id: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
id: comment.id,
|
||||
createdAt: comment.createdAt,
|
||||
updatedAt: comment.updatedAt,
|
||||
body: comment.body,
|
||||
author: {
|
||||
username: comment.author.username,
|
||||
bio: comment.author.bio,
|
||||
image: comment.author.image,
|
||||
following: comment.author.followedBy.some((follow: any) => follow.id === id),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteComment = async (id: number, userId: number) => {
|
||||
const comment = await prisma.comment.findFirst({
|
||||
where: {
|
||||
id,
|
||||
author: {
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
author: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!comment) {
|
||||
throw new HttpException(404, {});
|
||||
}
|
||||
|
||||
if (comment.author.id !== userId) {
|
||||
throw new HttpException(403, {
|
||||
message: 'You are not authorized to delete this comment',
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.comment.delete({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const favoriteArticle = async (slugPayload: string, id: number) => {
|
||||
const { _count, ...article } = await prisma.article.update({
|
||||
where: {
|
||||
slug: slugPayload,
|
||||
},
|
||||
data: {
|
||||
favoritedBy: {
|
||||
connect: {
|
||||
id: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tagList: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
favoritedBy: true,
|
||||
_count: {
|
||||
select: {
|
||||
favoritedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = {
|
||||
...article,
|
||||
author: profileMapper(article.author, id),
|
||||
tagList: article?.tagList.map((tag: Tag) => tag.name),
|
||||
favorited: article.favoritedBy.some((favorited: any) => favorited.id === id),
|
||||
favoritesCount: _count?.favoritedBy,
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const unfavoriteArticle = async (slugPayload: string, id: number) => {
|
||||
const { _count, ...article } = await prisma.article.update({
|
||||
where: {
|
||||
slug: slugPayload,
|
||||
},
|
||||
data: {
|
||||
favoritedBy: {
|
||||
disconnect: {
|
||||
id: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tagList: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
author: {
|
||||
select: {
|
||||
username: true,
|
||||
bio: true,
|
||||
image: true,
|
||||
followedBy: true,
|
||||
},
|
||||
},
|
||||
favoritedBy: true,
|
||||
_count: {
|
||||
select: {
|
||||
favoritedBy: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = {
|
||||
...article,
|
||||
author: profileMapper(article.author, id),
|
||||
tagList: article?.tagList.map((tag: Tag) => tag.name),
|
||||
favorited: article.favoritedBy.some((favorited: any) => favorited.id === id),
|
||||
favoritesCount: _count?.favoritedBy,
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
Reference in New Issue
Block a user