diff --git a/README.md b/README.md index ff6e444..98fbb61 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,17 @@ This repository contains a full-stack TypeScript application consisting of a NestJS backend and an Angular frontend. Prerequisites: - - Docker or a locally running MySQL installation - - NodeJS 16+ (tested with v18.13.0) - - Code Editor (VSCode is recommended) + +- Docker or a locally running MySQL installation +- NodeJS 16+ (tested with v18.13.0) +- Code Editor (VSCode is recommended) Getting started: - - Install the dependencies `npx yarn install`. - - If you don't have a local MySQL installation, start one in docker: `docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=conduit -e MYSQL_DATABASE=conduit -e MYSQL_USER=conduit -e MYSQL_PASSWORD=conduit mysql:8.1` - - Adjust the `apps/backend/mikro-orm.config.ts` with your MySQL credentials (they already match the ones from the docker command above). - - Start the app (both backend and frontend at once): `npm start`. - - After the backend successfully starts, in a new terminal `npm run seed` to seed the database with some initial data. - - You can now access the UI at http://localhost:4200 and login with `jcosten0@purevolume.com` / `password`. - - You can also find the backend API spec at http://localhost:3000/docs. + +- Install the dependencies `npx yarn install`. +- If you don't have a local MySQL installation, start one in docker: `docker run -d -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=conduit -e MYSQL_DATABASE=conduit -e MYSQL_USER=conduit -e MYSQL_PASSWORD=conduit mysql:8.1` +- Adjust the `apps/backend/mikro-orm.config.ts` with your MySQL credentials (they already match the ones from the docker command above). +- Start the app (both backend and frontend at once): `npm start`. +- After the backend successfully starts, in a new terminal `npm run seed` to seed the database with some initial data. +- You can now access the UI at http://localhost:4200 and login with `jcosten0@purevolume.com` / `password`. +- You can also find the backend API spec at http://localhost:3000/docs. diff --git a/apps/backend/src/article/article.entity.ts b/apps/backend/src/article/article.entity.ts index 5e7b831..219dbff 100644 --- a/apps/backend/src/article/article.entity.ts +++ b/apps/backend/src/article/article.entity.ts @@ -1,10 +1,12 @@ import { ArrayType, Collection, + DateTimeType, Entity, EntityDTO, ManyToOne, OneToMany, + Platform, PrimaryKey, Property, wrap, @@ -13,6 +15,7 @@ import slug from 'slug'; import { User } from '../user/user.entity'; import { Comment } from './comment.entity'; +import { MySqlPlatform } from '@mikro-orm/mysql'; @Entity() export class Article { @@ -31,7 +34,9 @@ export class Article { @Property() body = ''; - @Property({ type: 'date' }) + @Property({ type: 'datetime', onUpdate(entity: Article) { + return entity.createdAt.toString().replace('T', ' ').replace('Z','') + }, }) createdAt = new Date(); @Property({ type: 'date', onUpdate: () => new Date() }) diff --git a/apps/backend/src/article/article.service.ts b/apps/backend/src/article/article.service.ts index a3f04fc..5b32d9c 100644 --- a/apps/backend/src/article/article.service.ts +++ b/apps/backend/src/article/article.service.ts @@ -154,24 +154,42 @@ export class ArticleService { { populate: ['followers', 'favorites', 'articles'] }, ); const article = new Article(user!, dto.title, dto.description, dto.body); - article.tagList.push(...dto.tagList); + + // Parse tags + const tagNames = dto.tagList.split(','); + article.tagList.push(...tagNames.map((name: string) => name.trim())); + + // Remove duplicates + article.tagList = [...new Set(article.tagList)]; + user?.articles.add(article); await this.em.flush(); + user?.articles.add(article); + await this.em.flush(); + return { article: article.toJSON(user!) }; } - async update(userId: number, slug: string, articleData: any): Promise { - const user = await this.userRepository.findOne( - { id: userId }, - { populate: ['followers', 'favorites', 'articles'] }, - ); - const article = await this.articleRepository.findOne({ slug }, { populate: ['author'] }); - wrap(article).assign(articleData); - await this.em.flush(); + async update(userId: number, slug: string, articleData: CreateArticleDto): Promise { - return { article: article!.toJSON(user!) }; - } + const user = await this.userRepository.findOne( + { id: userId }, + { populate: ['followers', 'favorites', 'articles'] } + ); + + const article = await this.articleRepository.findOne({ slug }, { populate: ['author'] }); + + const tagList = articleData.tagList.split(','); + wrap(article).assign({ + ...articleData, + tagList + }); + + await this.em.flush(); + + return { article: article!.toJSON(user!) }; +} async delete(slug: string) { return this.articleRepository.nativeDelete({ slug }); diff --git a/apps/backend/src/article/dto/create-article.dto.ts b/apps/backend/src/article/dto/create-article.dto.ts index a52d01d..401a378 100644 --- a/apps/backend/src/article/dto/create-article.dto.ts +++ b/apps/backend/src/article/dto/create-article.dto.ts @@ -2,5 +2,5 @@ export class CreateArticleDto { readonly title: string; readonly description: string; readonly body: string; - readonly tagList: string[]; + readonly tagList: string; } diff --git a/apps/backend/src/tag/tag.module.ts b/apps/backend/src/tag/tag.module.ts index 31eb785..c49e5ca 100644 --- a/apps/backend/src/tag/tag.module.ts +++ b/apps/backend/src/tag/tag.module.ts @@ -4,11 +4,12 @@ import { TagController } from './tag.controller'; import { Tag } from './tag.entity'; import { TagService } from './tag.service'; import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { Article } from '../article/article.entity'; @Module({ controllers: [TagController], exports: [], - imports: [MikroOrmModule.forFeature({ entities: [Tag] }), UserModule], + imports: [MikroOrmModule.forFeature({ entities: [Tag, Article] }), UserModule], providers: [TagService], }) export class TagModule {} diff --git a/apps/backend/src/tag/tag.service.ts b/apps/backend/src/tag/tag.service.ts index 6bf57b2..f60785b 100644 --- a/apps/backend/src/tag/tag.service.ts +++ b/apps/backend/src/tag/tag.service.ts @@ -3,16 +3,22 @@ import { EntityRepository } from '@mikro-orm/core'; import { InjectRepository } from '@mikro-orm/nestjs'; import { Tag } from './tag.entity'; import { ITagsRO } from './tag.interface'; +import { Article } from '../article/article.entity'; @Injectable() export class TagService { - constructor( - @InjectRepository(Tag) - private readonly tagRepository: EntityRepository, - ) {} + constructor(@InjectRepository(Article) private articleRepository: EntityRepository
) {} - async findAll(): Promise { - const tags = await this.tagRepository.findAll(); - return { tags: tags.map((tag) => tag.tag) }; + async findAll() { + + const articles = await this.articleRepository.findAll(); + + const tags = articles.reduce((acc: string[], article: Article) => { + return [...acc, ...article.tagList]; + }, [] as string[]); + + return { + tags: [...new Set(tags)] + } } }