From f8d58a6c70b4aa17ec79a55e524612ae2f6faaa6 Mon Sep 17 00:00:00 2001 From: Michael Dever <120821+michaeldever@users.noreply.github.com> Date: Tue, 19 Sep 2023 19:56:24 +0100 Subject: [PATCH] Task 2 --- apps/backend/src/app.module.ts | 3 +- apps/backend/src/article/article.entity.ts | 9 ++-- apps/backend/src/article/article.service.ts | 39 ++++++++-------- apps/backend/src/roster/roster-user.dto.ts | 7 +++ apps/backend/src/roster/roster.controller.ts | 12 +++++ apps/backend/src/roster/roster.module.ts | 12 +++++ apps/backend/src/roster/roster.service.ts | 46 +++++++++++++++++++ apps/backend/src/tag/tag.service.ts | 5 +- apps/frontend/src/app/app.config.ts | 5 ++ .../app/layout/navbar/navbar.component.html | 6 +++ libs/home/src/lib/roster/roster.component.css | 34 ++++++++++++++ .../home/src/lib/roster/roster.component.html | 28 +++++++++++ libs/home/src/lib/roster/roster.component.ts | 36 +++++++++++++++ libs/home/src/lib/roster/roster.service.ts | 16 +++++++ libs/home/src/lib/roster/roster.type.ts | 7 +++ 15 files changed, 238 insertions(+), 27 deletions(-) create mode 100644 apps/backend/src/roster/roster-user.dto.ts create mode 100644 apps/backend/src/roster/roster.controller.ts create mode 100644 apps/backend/src/roster/roster.module.ts create mode 100644 apps/backend/src/roster/roster.service.ts create mode 100644 libs/home/src/lib/roster/roster.component.css create mode 100644 libs/home/src/lib/roster/roster.component.html create mode 100644 libs/home/src/lib/roster/roster.component.ts create mode 100644 libs/home/src/lib/roster/roster.service.ts create mode 100644 libs/home/src/lib/roster/roster.type.ts diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index c7407ad..231a1b8 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -8,9 +8,10 @@ import { ProfileModule } from './profile/profile.module'; import { TagModule } from './tag/tag.module'; import { UserModule } from './user/user.module'; import ormConfig from '../mikro-orm.config'; +import { RosterModule } from './roster/roster.module'; @Module({ controllers: [AppController], - imports: [MikroOrmModule.forRoot(ormConfig), ArticleModule, UserModule, ProfileModule, TagModule], + imports: [MikroOrmModule.forRoot(ormConfig), ArticleModule, UserModule, ProfileModule, TagModule, RosterModule], providers: [], }) export class AppModule implements NestModule, OnModuleInit { diff --git a/apps/backend/src/article/article.entity.ts b/apps/backend/src/article/article.entity.ts index 219dbff..62a0dda 100644 --- a/apps/backend/src/article/article.entity.ts +++ b/apps/backend/src/article/article.entity.ts @@ -34,9 +34,12 @@ export class Article { @Property() body = ''; - @Property({ type: 'datetime', onUpdate(entity: Article) { - return entity.createdAt.toString().replace('T', ' ').replace('Z','') - }, }) + @Property({ + type: 'datetime', + onUpdate(entity: Article) { + return new Date(entity.createdAt).toISOString().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 5b32d9c..7f129a3 100644 --- a/apps/backend/src/article/article.service.ts +++ b/apps/backend/src/article/article.service.ts @@ -156,40 +156,39 @@ export class ArticleService { const article = new Article(user!, dto.title, dto.description, dto.body); // Parse tags - const tagNames = dto.tagList.split(','); + const tagNames = dto.tagList.split(','); article.tagList.push(...tagNames.map((name: string) => name.trim())); - + // Remove duplicates - article.tagList = [...new Set(article.tagList)]; - + article.tagList = [...new Set(article.tagList)]; + user?.articles.add(article); await this.em.flush(); - 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: CreateArticleDto): Promise { + const user = await this.userRepository.findOne( + { id: userId }, + { populate: ['followers', 'favorites', 'articles'] }, + ); - const user = await this.userRepository.findOne( - { id: userId }, - { populate: ['followers', 'favorites', 'articles'] } - ); - - const article = await this.articleRepository.findOne({ slug }, { populate: ['author'] }); + const article = await this.articleRepository.findOne({ slug }, { populate: ['author'] }); - const tagList = articleData.tagList.split(','); - wrap(article).assign({ - ...articleData, - tagList - }); + const tagList = articleData.tagList.split(','); + wrap(article).assign({ + ...articleData, + tagList, + }); - await this.em.flush(); + await this.em.flush(); - return { article: article!.toJSON(user!) }; -} + return { article: article!.toJSON(user!) }; + } async delete(slug: string) { return this.articleRepository.nativeDelete({ slug }); diff --git a/apps/backend/src/roster/roster-user.dto.ts b/apps/backend/src/roster/roster-user.dto.ts new file mode 100644 index 0000000..1d5392b --- /dev/null +++ b/apps/backend/src/roster/roster-user.dto.ts @@ -0,0 +1,7 @@ +export interface RosterUser { + username: string; + url: string; + articlesCount: number; + totalFavorites: number; + firstArticleDate?: Date; +} diff --git a/apps/backend/src/roster/roster.controller.ts b/apps/backend/src/roster/roster.controller.ts new file mode 100644 index 0000000..0e1a6dc --- /dev/null +++ b/apps/backend/src/roster/roster.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { RosterService } from './roster.service'; + +@Controller('roster') +export class RosterController { + constructor(private readonly rosterService: RosterService) {} + + @Get() + async getRoster() { + return this.rosterService.getRoster(); + } +} diff --git a/apps/backend/src/roster/roster.module.ts b/apps/backend/src/roster/roster.module.ts new file mode 100644 index 0000000..099f286 --- /dev/null +++ b/apps/backend/src/roster/roster.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { RosterController } from './roster.controller'; +import { RosterService } from './roster.service'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { User } from '../user/user.entity'; + +@Module({ + controllers: [RosterController], + imports: [MikroOrmModule.forFeature({ entities: [User] })], + providers: [RosterService], +}) +export class RosterModule {} diff --git a/apps/backend/src/roster/roster.service.ts b/apps/backend/src/roster/roster.service.ts new file mode 100644 index 0000000..80f3833 --- /dev/null +++ b/apps/backend/src/roster/roster.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { User } from '../user/user.entity'; +import { UserRepository } from '../user/user.repository'; +import { Article } from '../article/article.entity'; + +export interface RosterUser { + username: string; + url: string; + articlesCount: number; + totalFavorites: number; + firstArticleDate?: Date; +} + +@Injectable() +export class RosterService { + constructor(private userRepository: UserRepository) {} + + async getRoster(): Promise { + const users = await this.userRepository.findAll({ + populate: ['articles'], + }); + + users.sort((a, b) => { + return ( + b.articles.getItems().reduce((sum: number, article: Article) => { + return sum + article.favoritesCount; + }, 0) - + a.articles.getItems().reduce((sum: number, article: Article) => { + return sum + article.favoritesCount; + }, 0) + ); + }); + + return users.map((user) => { + return { + username: user.username, + url: `/profile/${user.username}`, + articlesCount: user.articles.length, + totalFavorites: user.articles.getItems().reduce((sum, article) => { + return sum + article.favoritesCount; + }, 0), + firstArticleDate: user.articles[0]?.createdAt, + }; + }); + } +} diff --git a/apps/backend/src/tag/tag.service.ts b/apps/backend/src/tag/tag.service.ts index f60785b..6d87b7e 100644 --- a/apps/backend/src/tag/tag.service.ts +++ b/apps/backend/src/tag/tag.service.ts @@ -10,7 +10,6 @@ export class TagService { constructor(@InjectRepository(Article) private articleRepository: EntityRepository
) {} async findAll() { - const articles = await this.articleRepository.findAll(); const tags = articles.reduce((acc: string[], article: Article) => { @@ -18,7 +17,7 @@ export class TagService { }, [] as string[]); return { - tags: [...new Set(tags)] - } + tags: [...new Set(tags)], + }; } } diff --git a/apps/frontend/src/app/app.config.ts b/apps/frontend/src/app/app.config.ts index d9c80a6..5d49b5d 100644 --- a/apps/frontend/src/app/app.config.ts +++ b/apps/frontend/src/app/app.config.ts @@ -48,6 +48,11 @@ export const appConfig: ApplicationConfig = { path: 'profile', loadChildren: () => import('@realworld/profile/feature-profile').then((profile) => profile.PROFILE_ROUTES), }, + { + path: 'roster', + loadComponent: () => + import('@realworld/home/src/lib/roster/roster.component').then((roster) => roster.RosterListComponent), + }, ]), provideStore({ auth: authFeature.reducer, diff --git a/apps/frontend/src/app/layout/navbar/navbar.component.html b/apps/frontend/src/app/layout/navbar/navbar.component.html index f152b72..8a07666 100644 --- a/apps/frontend/src/app/layout/navbar/navbar.component.html +++ b/apps/frontend/src/app/layout/navbar/navbar.component.html @@ -13,6 +13,9 @@ + @@ -37,6 +40,9 @@  Settings +