diff --git a/src/app/app.component.ts b/src/app/app.component.ts deleted file mode 100644 index b7354c015..000000000 --- a/src/app/app.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { HeaderComponent } from './core/layout/header.component'; -import { RouterOutlet } from '@angular/router'; -import { FooterComponent } from './core/layout/footer.component'; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - imports: [HeaderComponent, RouterOutlet, FooterComponent], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class AppComponent {} diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 3bb56bc04..734f28c09 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -3,14 +3,14 @@ import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; -import { JwtService } from './core/auth/services/jwt.service'; -import { UserService } from './core/auth/services/user.service'; -import { apiInterceptor } from './core/interceptors/api.interceptor'; -import { tokenInterceptor } from './core/interceptors/token.interceptor'; -import { errorInterceptor } from './core/interceptors/error.interceptor'; +import { Jwt } from './core/auth/services/jwt'; +import { UserAuth } from './core/auth/services/user-auth'; +import { apiInterceptor } from './core/interceptors/api-interceptor'; +import { tokenInterceptor } from './core/interceptors/token-interceptor'; +import { errorInterceptor } from './core/interceptors/error-interceptor'; import { EMPTY } from 'rxjs'; -export function initAuth(jwtService: JwtService, userService: UserService) { +export function initAuth(jwtService: Jwt, userService: UserAuth) { return () => (jwtService.getToken() ? userService.getCurrentUser() : EMPTY); } @@ -20,7 +20,7 @@ export const appConfig: ApplicationConfig = { provideRouter(routes), provideHttpClient(withInterceptors([apiInterceptor, tokenInterceptor, errorInterceptor])), provideAppInitializer(() => { - const initializerFn = initAuth(inject(JwtService), inject(UserService)); + const initializerFn = initAuth(inject(Jwt), inject(UserAuth)); return initializerFn(); }), ], diff --git a/src/app/app.component.html b/src/app/app.html similarity index 100% rename from src/app/app.component.html rename to src/app/app.html diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 8528accfc..0dd762e20 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,31 +1,31 @@ import { Routes } from '@angular/router'; import { inject } from '@angular/core'; -import { UserService } from './core/auth/services/user.service'; +import { UserAuth } from './core/auth/services/user-auth'; import { map } from 'rxjs/operators'; export const routes: Routes = [ { path: '', - loadComponent: () => import('./features/article/pages/home/home.component'), + loadComponent: () => import('./features/article/pages/home/home'), }, { path: 'tag/:tag', - loadComponent: () => import('./features/article/pages/home/home.component'), + loadComponent: () => import('./features/article/pages/home/home'), }, { path: 'login', - loadComponent: () => import('./core/auth/auth.component'), - canActivate: [() => inject(UserService).isAuthenticated.pipe(map(isAuth => !isAuth))], + loadComponent: () => import('./core/auth/auth'), + canActivate: [() => inject(UserAuth).isAuthenticated.pipe(map(isAuth => !isAuth))], }, { path: 'register', - loadComponent: () => import('./core/auth/auth.component'), - canActivate: [() => inject(UserService).isAuthenticated.pipe(map(isAuth => !isAuth))], + loadComponent: () => import('./core/auth/auth'), + canActivate: [() => inject(UserAuth).isAuthenticated.pipe(map(isAuth => !isAuth))], }, { path: 'settings', - loadComponent: () => import('./features/settings/settings.component'), - canActivate: [() => inject(UserService).isAuthenticated], + loadComponent: () => import('./features/settings/settings'), + canActivate: [() => inject(UserAuth).isAuthenticated], }, { path: 'profile', @@ -36,18 +36,18 @@ export const routes: Routes = [ children: [ { path: '', - loadComponent: () => import('./features/article/pages/editor/editor.component'), - canActivate: [() => inject(UserService).isAuthenticated], + loadComponent: () => import('./features/article/pages/editor/editor'), + canActivate: [() => inject(UserAuth).isAuthenticated], }, { path: ':slug', - loadComponent: () => import('./features/article/pages/editor/editor.component'), - canActivate: [() => inject(UserService).isAuthenticated], + loadComponent: () => import('./features/article/pages/editor/editor'), + canActivate: [() => inject(UserAuth).isAuthenticated], }, ], }, { path: 'article/:slug', - loadComponent: () => import('./features/article/pages/article/article.component'), + loadComponent: () => import('./features/article/pages/article/article'), }, ]; diff --git a/src/app/app.ts b/src/app/app.ts new file mode 100644 index 000000000..06bb79003 --- /dev/null +++ b/src/app/app.ts @@ -0,0 +1,12 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { Header } from './core/layout/header'; +import { RouterOutlet } from '@angular/router'; +import { Footer } from './core/layout/footer'; + +@Component({ + selector: 'app-root', + templateUrl: './app.html', + imports: [Header, RouterOutlet, Footer], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class App {} diff --git a/src/app/core/auth/auth.component.html b/src/app/core/auth/auth.html similarity index 100% rename from src/app/core/auth/auth.component.html rename to src/app/core/auth/auth.html diff --git a/src/app/core/auth/auth.component.ts b/src/app/core/auth/auth.ts similarity index 86% rename from src/app/core/auth/auth.component.ts rename to src/app/core/auth/auth.ts index 555ab27ca..71e44d3ca 100644 --- a/src/app/core/auth/auth.component.ts +++ b/src/app/core/auth/auth.ts @@ -1,9 +1,9 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal } from '@angular/core'; import { Validators, FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { ListErrorsComponent } from '../../shared/components/list-errors.component'; +import { ListErrors } from '../../shared/components/list-errors'; import { Errors } from '../models/errors.model'; -import { UserService } from './services/user.service'; +import { UserAuth } from './services/user-auth'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; interface AuthForm { @@ -14,11 +14,11 @@ interface AuthForm { @Component({ selector: 'app-auth-page', - templateUrl: './auth.component.html', - imports: [RouterLink, ListErrorsComponent, ReactiveFormsModule], + templateUrl: './auth.html', + imports: [RouterLink, ListErrors, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class AuthComponent implements OnInit { +export default class Auth implements OnInit { authType = ''; title = ''; errors = signal({ errors: {} }); @@ -29,7 +29,7 @@ export default class AuthComponent implements OnInit { constructor( private readonly route: ActivatedRoute, private readonly router: Router, - private readonly userService: UserService, + private readonly userService: UserAuth, ) { this.authForm = new FormGroup({ email: new FormControl('', { diff --git a/src/app/core/auth/if-authenticated.directive.ts b/src/app/core/auth/if-authenticated.ts similarity index 87% rename from src/app/core/auth/if-authenticated.directive.ts rename to src/app/core/auth/if-authenticated.ts index 6c8c3228a..bf969b4d5 100644 --- a/src/app/core/auth/if-authenticated.directive.ts +++ b/src/app/core/auth/if-authenticated.ts @@ -1,16 +1,16 @@ import { DestroyRef, Directive, inject, Input, OnInit, signal, TemplateRef, ViewContainerRef } from '@angular/core'; -import { UserService } from './services/user.service'; +import { UserAuth } from './services/user-auth'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Directive({ selector: '[ifAuthenticated]', standalone: true, }) -export class IfAuthenticatedDirective implements OnInit { +export class IfAuthenticated implements OnInit { destroyRef = inject(DestroyRef); constructor( private templateRef: TemplateRef, - private userService: UserService, + private userService: UserAuth, private viewContainer: ViewContainerRef, ) {} diff --git a/src/app/core/auth/services/jwt.service.spec.ts b/src/app/core/auth/services/jwt.spec.ts similarity index 98% rename from src/app/core/auth/services/jwt.service.spec.ts rename to src/app/core/auth/services/jwt.spec.ts index 7ede63e3b..0b44e539c 100644 --- a/src/app/core/auth/services/jwt.service.spec.ts +++ b/src/app/core/auth/services/jwt.spec.ts @@ -3,10 +3,10 @@ import 'zone.js/testing'; import { describe, it, expect, beforeEach, afterEach, beforeAll, vi } from 'vitest'; import { TestBed, getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -import { JwtService } from './jwt.service'; +import { Jwt } from './jwt'; -describe('JwtService', () => { - let service: JwtService; +describe('Jwt', () => { + let service: Jwt; let localStorageSpy: any; beforeAll(() => { @@ -30,10 +30,10 @@ describe('JwtService', () => { }); TestBed.configureTestingModule({ - providers: [JwtService], + providers: [Jwt], }); - service = TestBed.inject(JwtService); + service = TestBed.inject(Jwt); }); afterEach(() => { diff --git a/src/app/core/auth/services/jwt.service.ts b/src/app/core/auth/services/jwt.ts similarity index 92% rename from src/app/core/auth/services/jwt.service.ts rename to src/app/core/auth/services/jwt.ts index 691f43cfd..685bf8818 100644 --- a/src/app/core/auth/services/jwt.service.ts +++ b/src/app/core/auth/services/jwt.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) -export class JwtService { +export class Jwt { getToken(): string { return window.localStorage['jwtToken']; } diff --git a/src/app/core/auth/services/user.service.spec.ts b/src/app/core/auth/services/user-auth.spec.ts similarity index 96% rename from src/app/core/auth/services/user.service.spec.ts rename to src/app/core/auth/services/user-auth.spec.ts index 293414f24..a44de5442 100644 --- a/src/app/core/auth/services/user.service.spec.ts +++ b/src/app/core/auth/services/user-auth.spec.ts @@ -6,16 +6,16 @@ import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@ang import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { Router } from '@angular/router'; import { firstValueFrom } from 'rxjs'; -import { UserService } from './user.service'; -import { JwtService } from './jwt.service'; +import { UserAuth } from './user-auth'; +import { Jwt } from './jwt'; import { User } from '../user.model'; -describe('UserService', () => { +describe('UserAuth', () => { beforeAll(() => { getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); }); - let service: UserService; + let service: UserAuth; let httpMock: HttpTestingController; let jwtService: any; let router: any; @@ -40,10 +40,10 @@ describe('UserService', () => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [UserService, { provide: JwtService, useValue: jwtService }, { provide: Router, useValue: router }], + providers: [UserAuth, { provide: Jwt, useValue: jwtService }, { provide: Router, useValue: router }], }); - service = TestBed.inject(UserService); + service = TestBed.inject(UserAuth); httpMock = TestBed.inject(HttpTestingController); }); @@ -286,7 +286,7 @@ describe('UserService', () => { }); describe('setAuth', () => { - it('should save token to JwtService', () => { + it('should save token to Jwt', () => { service.setAuth(mockUser); expect(jwtService.saveToken).toHaveBeenCalledWith(mockUser.token); }); @@ -305,7 +305,7 @@ describe('UserService', () => { }); describe('purgeAuth', () => { - it('should destroy token in JwtService', () => { + it('should destroy token in Jwt', () => { service.purgeAuth(); expect(jwtService.destroyToken).toHaveBeenCalled(); }); diff --git a/src/app/core/auth/services/user.service.ts b/src/app/core/auth/services/user-auth.ts similarity index 94% rename from src/app/core/auth/services/user.service.ts rename to src/app/core/auth/services/user-auth.ts index 5c607164a..87d662e66 100644 --- a/src/app/core/auth/services/user.service.ts +++ b/src/app/core/auth/services/user-auth.ts @@ -1,14 +1,14 @@ import { Injectable } from '@angular/core'; import { Observable, BehaviorSubject } from 'rxjs'; -import { JwtService } from './jwt.service'; +import { Jwt } from './jwt'; import { map, distinctUntilChanged, tap, shareReplay } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; import { User } from '../user.model'; import { Router } from '@angular/router'; @Injectable({ providedIn: 'root' }) -export class UserService { +export class UserAuth { private currentUserSubject = new BehaviorSubject(null); public currentUser = this.currentUserSubject.asObservable().pipe(distinctUntilChanged()); @@ -16,7 +16,7 @@ export class UserService { constructor( private readonly http: HttpClient, - private readonly jwtService: JwtService, + private readonly jwtService: Jwt, private readonly router: Router, ) {} diff --git a/src/app/core/interceptors/api.interceptor.ts b/src/app/core/interceptors/api-interceptor.ts similarity index 100% rename from src/app/core/interceptors/api.interceptor.ts rename to src/app/core/interceptors/api-interceptor.ts diff --git a/src/app/core/interceptors/error.interceptor.ts b/src/app/core/interceptors/error-interceptor.ts similarity index 100% rename from src/app/core/interceptors/error.interceptor.ts rename to src/app/core/interceptors/error-interceptor.ts diff --git a/src/app/core/interceptors/token.interceptor.ts b/src/app/core/interceptors/token-interceptor.ts similarity index 74% rename from src/app/core/interceptors/token.interceptor.ts rename to src/app/core/interceptors/token-interceptor.ts index 40d594315..9b6fe8946 100644 --- a/src/app/core/interceptors/token.interceptor.ts +++ b/src/app/core/interceptors/token-interceptor.ts @@ -1,9 +1,9 @@ import { inject } from '@angular/core'; import { HttpInterceptorFn } from '@angular/common/http'; -import { JwtService } from '../auth/services/jwt.service'; +import { Jwt } from '../auth/services/jwt'; export const tokenInterceptor: HttpInterceptorFn = (req, next) => { - const token = inject(JwtService).getToken(); + const token = inject(Jwt).getToken(); const request = req.clone({ setHeaders: { diff --git a/src/app/core/layout/footer.component.html b/src/app/core/layout/footer.html similarity index 100% rename from src/app/core/layout/footer.component.html rename to src/app/core/layout/footer.html diff --git a/src/app/core/layout/footer.component.ts b/src/app/core/layout/footer.ts similarity index 81% rename from src/app/core/layout/footer.component.ts rename to src/app/core/layout/footer.ts index 2ae794094..be0317318 100644 --- a/src/app/core/layout/footer.component.ts +++ b/src/app/core/layout/footer.ts @@ -4,10 +4,10 @@ import { RouterLink } from '@angular/router'; @Component({ selector: 'app-layout-footer', - templateUrl: './footer.component.html', + templateUrl: './footer.html', changeDetection: ChangeDetectionStrategy.OnPush, imports: [DatePipe, RouterLink], }) -export class FooterComponent { +export class Footer { today: number = Date.now(); } diff --git a/src/app/core/layout/header.component.html b/src/app/core/layout/header.html similarity index 100% rename from src/app/core/layout/header.component.html rename to src/app/core/layout/header.html diff --git a/src/app/core/layout/header.component.ts b/src/app/core/layout/header.ts similarity index 55% rename from src/app/core/layout/header.component.ts rename to src/app/core/layout/header.ts index 66c463ccf..19f72b875 100644 --- a/src/app/core/layout/header.component.ts +++ b/src/app/core/layout/header.ts @@ -1,15 +1,15 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; -import { UserService } from '../auth/services/user.service'; +import { UserAuth } from '../auth/services/user-auth'; import { RouterLink, RouterLinkActive } from '@angular/router'; import { AsyncPipe } from '@angular/common'; -import { IfAuthenticatedDirective } from '../auth/if-authenticated.directive'; +import { IfAuthenticated } from '../auth/if-authenticated'; @Component({ selector: 'app-layout-header', - templateUrl: './header.component.html', - imports: [RouterLinkActive, RouterLink, AsyncPipe, IfAuthenticatedDirective], + templateUrl: './header.html', + imports: [RouterLinkActive, RouterLink, AsyncPipe, IfAuthenticated], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class HeaderComponent { - currentUser$ = inject(UserService).currentUser; +export class Header { + currentUser$ = inject(UserAuth).currentUser; } diff --git a/src/app/features/article/components/article-comment.component.ts b/src/app/features/article/components/article-comment.ts similarity index 90% rename from src/app/features/article/components/article-comment.component.ts rename to src/app/features/article/components/article-comment.ts index 0a3551a14..333c93115 100644 --- a/src/app/features/article/components/article-comment.component.ts +++ b/src/app/features/article/components/article-comment.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, inject } from '@angular/core'; -import { UserService } from '../../../core/auth/services/user.service'; +import { UserAuth } from '../../../core/auth/services/user-auth'; import { User } from '../../../core/auth/user.model'; import { RouterLink } from '@angular/router'; import { map } from 'rxjs/operators'; @@ -39,11 +39,11 @@ import { AsyncPipe, DatePipe } from '@angular/common'; imports: [RouterLink, DatePipe, AsyncPipe], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ArticleCommentComponent { +export class ArticleComment { @Input() comment!: Comment; @Output() delete = new EventEmitter(); - canModify$ = inject(UserService).currentUser.pipe( + canModify$ = inject(UserAuth).currentUser.pipe( map((userData: User | null) => userData?.username === this.comment.author.username), ); } diff --git a/src/app/features/article/components/article-list.component.ts b/src/app/features/article/components/article-list.ts similarity index 90% rename from src/app/features/article/components/article-list.component.ts rename to src/app/features/article/components/article-list.ts index 5d3b3a6a6..0e62fa7db 100644 --- a/src/app/features/article/components/article-list.component.ts +++ b/src/app/features/article/components/article-list.ts @@ -10,10 +10,10 @@ import { signal, SimpleChanges, } from '@angular/core'; -import { ArticlesService } from '../services/articles.service'; +import { Articles } from '../services/articles'; import { ArticleListConfig } from '../models/article-list-config.model'; -import { Article } from '../models/article.model'; -import { ArticlePreviewComponent } from './article-preview.component'; +import { ArticleModel } from '../models/article.model'; +import { ArticlePreview } from './article-preview'; import { NgClass } from '@angular/common'; import { RouterLink } from '@angular/router'; import { LoadingState } from '../../../core/models/loading-state.model'; @@ -53,7 +53,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; } `, - imports: [ArticlePreviewComponent, NgClass, RouterLink], + imports: [ArticlePreview, NgClass, RouterLink], styles: ` .page-link { cursor: pointer; @@ -61,9 +61,9 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ArticleListComponent implements OnChanges { +export class ArticleList implements OnChanges { query!: ArticleListConfig; - results = signal([]); + results = signal([]); page = signal(1); totalPages = signal([]); loading = signal(LoadingState.NOT_LOADED); @@ -98,7 +98,7 @@ export class ArticleListComponent implements OnChanges { } } - constructor(private articlesService: ArticlesService) {} + constructor(private articlesService: Articles) {} setPageTo(pageNumber: number) { if (pageNumber !== this.page()) { diff --git a/src/app/features/article/components/article-meta.component.ts b/src/app/features/article/components/article-meta.ts similarity index 86% rename from src/app/features/article/components/article-meta.component.ts rename to src/app/features/article/components/article-meta.ts index 9aaedd9e4..8ad06e4d4 100644 --- a/src/app/features/article/components/article-meta.component.ts +++ b/src/app/features/article/components/article-meta.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { Article } from '../models/article.model'; +import { ArticleModel } from '../models/article.model'; import { RouterLink } from '@angular/router'; import { DatePipe } from '@angular/common'; @@ -26,6 +26,6 @@ import { DatePipe } from '@angular/common'; changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterLink, DatePipe], }) -export class ArticleMetaComponent { - @Input() article!: Article; +export class ArticleMeta { + @Input() article!: ArticleModel; } diff --git a/src/app/features/article/components/article-preview.component.ts b/src/app/features/article/components/article-preview.ts similarity index 77% rename from src/app/features/article/components/article-preview.component.ts rename to src/app/features/article/components/article-preview.ts index 593e85d60..a0cbbf692 100644 --- a/src/app/features/article/components/article-preview.component.ts +++ b/src/app/features/article/components/article-preview.ts @@ -1,9 +1,9 @@ import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core'; -import { Article } from '../models/article.model'; -import { ArticleMetaComponent } from './article-meta.component'; +import { ArticleModel } from '../models/article.model'; +import { ArticleMeta } from './article-meta'; import { RouterLink } from '@angular/router'; -import { FavoriteButtonComponent } from './favorite-button.component'; +import { FavoriteButton } from './favorite-button'; @Component({ selector: 'app-article-preview', @@ -29,14 +29,14 @@ import { FavoriteButtonComponent } from './favorite-button.component'; `, - imports: [ArticleMetaComponent, FavoriteButtonComponent, RouterLink], + imports: [ArticleMeta, FavoriteButton, RouterLink], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ArticlePreviewComponent { - article = signal
(null!); +export class ArticlePreview { + article = signal(null!); @Input({ required: true }) - set articleInput(value: Article) { + set articleInput(value: ArticleModel) { this.article.set(value); } diff --git a/src/app/features/article/components/favorite-button.component.ts b/src/app/features/article/components/favorite-button.ts similarity index 82% rename from src/app/features/article/components/favorite-button.component.ts rename to src/app/features/article/components/favorite-button.ts index 91c3b54c4..1d1b31654 100644 --- a/src/app/features/article/components/favorite-button.component.ts +++ b/src/app/features/article/components/favorite-button.ts @@ -11,9 +11,9 @@ import { import { Router } from '@angular/router'; import { EMPTY, switchMap } from 'rxjs'; import { NgClass } from '@angular/common'; -import { ArticlesService } from '../services/articles.service'; -import { UserService } from '../../../core/auth/services/user.service'; -import { Article } from '../models/article.model'; +import { Articles } from '../services/articles'; +import { UserAuth } from '../../../core/auth/services/user-auth'; +import { ArticleModel } from '../models/article.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ @@ -34,17 +34,17 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class FavoriteButtonComponent { +export class FavoriteButton { destroyRef = inject(DestroyRef); isSubmitting = signal(false); - @Input() article!: Article; + @Input() article!: ArticleModel; @Output() toggle = new EventEmitter(); constructor( - private readonly articleService: ArticlesService, + private readonly articleService: Articles, private readonly router: Router, - private readonly userService: UserService, + private readonly userService: UserAuth, ) {} toggleFavorite(): void { diff --git a/src/app/features/article/models/article.model.ts b/src/app/features/article/models/article.model.ts index a7b07584b..d85feb17c 100644 --- a/src/app/features/article/models/article.model.ts +++ b/src/app/features/article/models/article.model.ts @@ -1,6 +1,6 @@ -import { Profile } from '../../profile/models/profile.model'; +import { ProfileModel } from '../../profile/models/profile.model'; -export interface Article { +export interface ArticleModel { slug: string; title: string; description: string; @@ -10,5 +10,5 @@ export interface Article { updatedAt: string; favorited: boolean; favoritesCount: number; - author: Profile; + author: ProfileModel; } diff --git a/src/app/features/article/models/comment.model.ts b/src/app/features/article/models/comment.model.ts index 6a6350b5b..2fecac137 100644 --- a/src/app/features/article/models/comment.model.ts +++ b/src/app/features/article/models/comment.model.ts @@ -1,8 +1,8 @@ -import { Profile } from '../../profile/models/profile.model'; +import { ProfileModel } from '../../profile/models/profile.model'; export interface Comment { id: string; body: string; createdAt: string; - author: Profile; + author: ProfileModel; } diff --git a/src/app/features/article/pages/article/article.component.html b/src/app/features/article/pages/article/article.html similarity index 100% rename from src/app/features/article/pages/article/article.component.html rename to src/app/features/article/pages/article/article.html diff --git a/src/app/features/article/pages/article/article.component.ts b/src/app/features/article/pages/article/article.ts similarity index 70% rename from src/app/features/article/pages/article/article.component.ts rename to src/app/features/article/pages/article/article.ts index a864c062f..a68a750bc 100644 --- a/src/app/features/article/pages/article/article.component.ts +++ b/src/app/features/article/pages/article/article.ts @@ -2,46 +2,46 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { User } from '../../../../core/auth/user.model'; -import { Article } from '../../models/article.model'; -import { ArticlesService } from '../../services/articles.service'; -import { CommentsService } from '../../services/comments.service'; -import { UserService } from '../../../../core/auth/services/user.service'; -import { ArticleMetaComponent } from '../../components/article-meta.component'; +import { ArticleModel } from '../../models/article.model'; +import { Articles } from '../../services/articles'; +import { Comments } from '../../services/comments'; +import { UserAuth } from '../../../../core/auth/services/user-auth'; +import { ArticleMeta } from '../../components/article-meta'; import { AsyncPipe, NgClass } from '@angular/common'; -import { MarkdownPipe } from '../../../../shared/pipes/markdown.pipe'; -import { ListErrorsComponent } from '../../../../shared/components/list-errors.component'; -import { ArticleCommentComponent } from '../../components/article-comment.component'; +import { MarkdownPipe } from '../../../../shared/pipes/markdown-pipe'; +import { ListErrors } from '../../../../shared/components/list-errors'; +import { ArticleComment } from '../../components/article-comment'; import { catchError } from 'rxjs/operators'; import { combineLatest, throwError } from 'rxjs'; import { Comment } from '../../models/comment.model'; -import { IfAuthenticatedDirective } from '../../../../core/auth/if-authenticated.directive'; +import { IfAuthenticated } from '../../../../core/auth/if-authenticated'; import { Errors } from '../../../../core/models/errors.model'; -import { Profile } from '../../../profile/models/profile.model'; +import { ProfileModel } from '../../../profile/models/profile.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { FavoriteButtonComponent } from '../../components/favorite-button.component'; -import { FollowButtonComponent } from '../../../profile/components/follow-button.component'; +import { FavoriteButton } from '../../components/favorite-button'; +import { FollowButton } from '../../../profile/components/follow-button'; @Component({ selector: 'app-article-page', - templateUrl: './article.component.html', + templateUrl: './article.html', imports: [ - ArticleMetaComponent, + ArticleMeta, RouterLink, NgClass, - FollowButtonComponent, - FavoriteButtonComponent, + FollowButton, + FavoriteButton, MarkdownPipe, AsyncPipe, - ListErrorsComponent, + ListErrors, FormsModule, - ArticleCommentComponent, + ArticleComment, ReactiveFormsModule, - IfAuthenticatedDirective, + IfAuthenticated, ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class ArticleComponent implements OnInit { - article = signal
(null!); +export default class Article implements OnInit { + article = signal(null!); currentUser = signal(null); comments = signal([]); canModify = signal(false); @@ -55,10 +55,10 @@ export default class ArticleComponent implements OnInit { constructor( private readonly route: ActivatedRoute, - private readonly articleService: ArticlesService, - private readonly commentsService: CommentsService, + private readonly articleService: Articles, + private readonly commentsService: Comments, private readonly router: Router, - private readonly userService: UserService, + private readonly userService: UserAuth, ) {} ngOnInit(): void { @@ -87,7 +87,7 @@ export default class ArticleComponent implements OnInit { })); } - toggleFollowing(profile: Profile): void { + toggleFollowing(profile: ProfileModel): void { this.article.update(article => ({ ...article, author: { ...article.author, following: profile.following }, diff --git a/src/app/features/article/pages/editor/editor.component.html b/src/app/features/article/pages/editor/editor.html similarity index 100% rename from src/app/features/article/pages/editor/editor.component.html rename to src/app/features/article/pages/editor/editor.html diff --git a/src/app/features/article/pages/editor/editor.component.ts b/src/app/features/article/pages/editor/editor.ts similarity index 85% rename from src/app/features/article/pages/editor/editor.component.ts rename to src/app/features/article/pages/editor/editor.ts index 9faed2d13..4ffa8505d 100644 --- a/src/app/features/article/pages/editor/editor.component.ts +++ b/src/app/features/article/pages/editor/editor.ts @@ -3,9 +3,9 @@ import { FormControl, FormGroup, ReactiveFormsModule, UntypedFormGroup } from '@ import { ActivatedRoute, Router } from '@angular/router'; import { combineLatest } from 'rxjs'; import { Errors } from '../../../../core/models/errors.model'; -import { ArticlesService } from '../../services/articles.service'; -import { UserService } from '../../../../core/auth/services/user.service'; -import { ListErrorsComponent } from '../../../../shared/components/list-errors.component'; +import { Articles } from '../../services/articles'; +import { UserAuth } from '../../../../core/auth/services/user-auth'; +import { ListErrors } from '../../../../shared/components/list-errors'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; interface ArticleForm { @@ -16,11 +16,11 @@ interface ArticleForm { @Component({ selector: 'app-editor-page', - templateUrl: './editor.component.html', - imports: [ListErrorsComponent, ReactiveFormsModule], + templateUrl: './editor.html', + imports: [ListErrors, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class EditorComponent implements OnInit { +export default class Editor implements OnInit { tagList = signal([]); articleForm: UntypedFormGroup = new FormGroup({ title: new FormControl('', { nonNullable: true }), @@ -34,10 +34,10 @@ export default class EditorComponent implements OnInit { destroyRef = inject(DestroyRef); constructor( - private readonly articleService: ArticlesService, + private readonly articleService: Articles, private readonly route: ActivatedRoute, private readonly router: Router, - private readonly userService: UserService, + private readonly userService: UserAuth, ) {} ngOnInit() { diff --git a/src/app/features/article/pages/home/home.component.css b/src/app/features/article/pages/home/home.css similarity index 100% rename from src/app/features/article/pages/home/home.component.css rename to src/app/features/article/pages/home/home.css diff --git a/src/app/features/article/pages/home/home.component.html b/src/app/features/article/pages/home/home.html similarity index 100% rename from src/app/features/article/pages/home/home.component.html rename to src/app/features/article/pages/home/home.html diff --git a/src/app/features/article/pages/home/home.component.ts b/src/app/features/article/pages/home/home.ts similarity index 80% rename from src/app/features/article/pages/home/home.component.ts rename to src/app/features/article/pages/home/home.ts index 501288812..29214962f 100644 --- a/src/app/features/article/pages/home/home.component.ts +++ b/src/app/features/article/pages/home/home.ts @@ -1,31 +1,31 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal } from '@angular/core'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; -import { TagsService } from '../../services/tags.service'; +import { Tags } from '../../services/tags'; import { ArticleListConfig } from '../../models/article-list-config.model'; import { NgClass } from '@angular/common'; -import { ArticleListComponent } from '../../components/article-list.component'; +import { ArticleList } from '../../components/article-list'; import { combineLatest } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { UserService } from '../../../../core/auth/services/user.service'; +import { UserAuth } from '../../../../core/auth/services/user-auth'; import { RxLet } from '@rx-angular/template/let'; -import { IfAuthenticatedDirective } from '../../../../core/auth/if-authenticated.directive'; +import { IfAuthenticated } from '../../../../core/auth/if-authenticated'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'app-home-page', - templateUrl: './home.component.html', - styleUrls: ['./home.component.css'], - imports: [NgClass, ArticleListComponent, RxLet, IfAuthenticatedDirective, RouterLink], + templateUrl: './home.html', + styleUrls: ['./home.css'], + imports: [NgClass, ArticleList, RxLet, IfAuthenticated, RouterLink], changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class HomeComponent implements OnInit { +export default class Home implements OnInit { isAuthenticated = signal(false); listConfig = signal({ type: 'all', filters: {}, }); currentPage = signal(1); - tags$ = inject(TagsService) + tags$ = inject(Tags) .getAll() .pipe(tap(() => this.tagsLoaded.set(true))); tagsLoaded = signal(false); @@ -35,7 +35,7 @@ export default class HomeComponent implements OnInit { constructor( private readonly router: Router, private readonly route: ActivatedRoute, - private readonly userService: UserService, + private readonly userService: UserAuth, ) {} ngOnInit(): void { diff --git a/src/app/features/article/services/articles.service.spec.ts b/src/app/features/article/services/articles.spec.ts similarity index 94% rename from src/app/features/article/services/articles.service.spec.ts rename to src/app/features/article/services/articles.spec.ts index a6a8af9a6..892cb99c4 100644 --- a/src/app/features/article/services/articles.service.spec.ts +++ b/src/app/features/article/services/articles.spec.ts @@ -5,19 +5,19 @@ import { TestBed, getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { firstValueFrom } from 'rxjs'; -import { ArticlesService } from './articles.service'; -import { Article } from '../models/article.model'; +import { Articles } from './articles'; +import { ArticleModel } from '../models/article.model'; import { ArticleListConfig } from '../models/article-list-config.model'; -describe('ArticlesService', () => { +describe('Articles', () => { beforeAll(() => { getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); }); - let service: ArticlesService; + let service: Articles; let httpMock: HttpTestingController; - const mockArticle: Article = { + const mockArticle: ArticleModel = { slug: 'test-article', title: 'Test Article', description: 'Test description', @@ -35,7 +35,7 @@ describe('ArticlesService', () => { }, }; - const mockArticleList: Article[] = [ + const mockArticleList: ArticleModel[] = [ mockArticle, { ...mockArticle, @@ -47,9 +47,9 @@ describe('ArticlesService', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [ArticlesService], + providers: [Articles], }); - service = TestBed.inject(ArticlesService); + service = TestBed.inject(Articles); httpMock = TestBed.inject(HttpTestingController); }); @@ -193,7 +193,7 @@ describe('ArticlesService', () => { describe('create', () => { it('should create new article', async () => { - const newArticle: Partial
= { + const newArticle: Partial = { title: 'New Article', description: 'New description', body: 'New body', @@ -209,7 +209,7 @@ describe('ArticlesService', () => { }); it('should handle validation errors', async () => { - const invalidArticle: Partial
= { + const invalidArticle: Partial = { title: '', description: '', body: '', @@ -224,7 +224,7 @@ describe('ArticlesService', () => { describe('update', () => { it('should update existing article', async () => { - const updates: Partial
= { + const updates: Partial = { slug: 'existing-article', title: 'Updated Title', description: 'Updated description', diff --git a/src/app/features/article/services/articles.service.ts b/src/app/features/article/services/articles.ts similarity index 55% rename from src/app/features/article/services/articles.service.ts rename to src/app/features/article/services/articles.ts index c4b816b98..2cbb8e3bb 100644 --- a/src/app/features/article/services/articles.service.ts +++ b/src/app/features/article/services/articles.ts @@ -3,13 +3,13 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ArticleListConfig } from '../models/article-list-config.model'; -import { Article } from '../models/article.model'; +import { ArticleModel } from '../models/article.model'; @Injectable({ providedIn: 'root' }) -export class ArticlesService { +export class Articles { constructor(private readonly http: HttpClient) {} - query(config: ArticleListConfig): Observable<{ articles: Article[]; articlesCount: number }> { + query(config: ArticleListConfig): Observable<{ articles: ArticleModel[]; articlesCount: number }> { // Convert any filters over to Angular's URLSearchParams let params = new HttpParams(); @@ -18,34 +18,36 @@ export class ArticlesService { params = params.set(key, config.filters[key]); }); - return this.http.get<{ articles: Article[]; articlesCount: number }>( + return this.http.get<{ articles: ArticleModel[]; articlesCount: number }>( '/articles' + (config.type === 'feed' ? '/feed' : ''), { params }, ); } - get(slug: string): Observable
{ - return this.http.get<{ article: Article }>(`/articles/${slug}`).pipe(map(data => data.article)); + get(slug: string): Observable { + return this.http.get<{ article: ArticleModel }>(`/articles/${slug}`).pipe(map(data => data.article)); } delete(slug: string): Observable { return this.http.delete(`/articles/${slug}`); } - create(article: Partial
): Observable
{ - return this.http.post<{ article: Article }>('/articles/', { article: article }).pipe(map(data => data.article)); + create(article: Partial): Observable { + return this.http + .post<{ article: ArticleModel }>('/articles/', { article: article }) + .pipe(map(data => data.article)); } - update(article: Partial
): Observable
{ + update(article: Partial): Observable { return this.http - .put<{ article: Article }>(`/articles/${article.slug}`, { + .put<{ article: ArticleModel }>(`/articles/${article.slug}`, { article: article, }) .pipe(map(data => data.article)); } - favorite(slug: string): Observable
{ - return this.http.post<{ article: Article }>(`/articles/${slug}/favorite`, {}).pipe(map(data => data.article)); + favorite(slug: string): Observable { + return this.http.post<{ article: ArticleModel }>(`/articles/${slug}/favorite`, {}).pipe(map(data => data.article)); } unfavorite(slug: string): Observable { diff --git a/src/app/features/article/services/comments.service.spec.ts b/src/app/features/article/services/comments.spec.ts similarity index 98% rename from src/app/features/article/services/comments.service.spec.ts rename to src/app/features/article/services/comments.spec.ts index a17f04d56..379c79efc 100644 --- a/src/app/features/article/services/comments.service.spec.ts +++ b/src/app/features/article/services/comments.spec.ts @@ -5,22 +5,21 @@ import { TestBed, getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { firstValueFrom } from 'rxjs'; -import { CommentsService } from './comments.service'; +import { Comments } from './comments'; import { Comment } from '../models/comment.model'; -describe('CommentsService', () => { +describe('Comments', () => { beforeAll(() => { getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); }); - let service: CommentsService; + let service: Comments; let httpMock: HttpTestingController; const mockComment: Comment = { id: '1', body: 'Test comment', createdAt: '2024-01-01', - updatedAt: '2024-01-02', author: { username: 'testuser', bio: 'Test bio', @@ -41,10 +40,10 @@ describe('CommentsService', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [CommentsService], + providers: [Comments], }); - service = TestBed.inject(CommentsService); + service = TestBed.inject(Comments); httpMock = TestBed.inject(HttpTestingController); }); diff --git a/src/app/features/article/services/comments.service.ts b/src/app/features/article/services/comments.ts similarity index 96% rename from src/app/features/article/services/comments.service.ts rename to src/app/features/article/services/comments.ts index 57ab62a42..91eed6edb 100644 --- a/src/app/features/article/services/comments.service.ts +++ b/src/app/features/article/services/comments.ts @@ -5,7 +5,7 @@ import { HttpClient } from '@angular/common/http'; import { Comment } from '../models/comment.model'; @Injectable({ providedIn: 'root' }) -export class CommentsService { +export class Comments { constructor(private readonly http: HttpClient) {} getAll(slug: string): Observable { diff --git a/src/app/features/article/services/tags.service.spec.ts b/src/app/features/article/services/tags.spec.ts similarity index 98% rename from src/app/features/article/services/tags.service.spec.ts rename to src/app/features/article/services/tags.spec.ts index b30d1584b..66a90302f 100644 --- a/src/app/features/article/services/tags.service.spec.ts +++ b/src/app/features/article/services/tags.spec.ts @@ -5,14 +5,14 @@ import { TestBed, getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { firstValueFrom } from 'rxjs'; -import { TagsService } from './tags.service'; +import { Tags } from './tags'; -describe('TagsService', () => { +describe('Tags', () => { beforeAll(() => { getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); }); - let service: TagsService; + let service: Tags; let httpMock: HttpTestingController; const mockTags: string[] = ['angular', 'typescript', 'testing', 'rxjs', 'javascript']; @@ -20,10 +20,10 @@ describe('TagsService', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [TagsService], + providers: [Tags], }); - service = TestBed.inject(TagsService); + service = TestBed.inject(Tags); httpMock = TestBed.inject(HttpTestingController); }); diff --git a/src/app/features/article/services/tags.service.ts b/src/app/features/article/services/tags.ts similarity index 93% rename from src/app/features/article/services/tags.service.ts rename to src/app/features/article/services/tags.ts index dcd9b6c04..78a8f29d7 100644 --- a/src/app/features/article/services/tags.service.ts +++ b/src/app/features/article/services/tags.ts @@ -4,7 +4,7 @@ import { map } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) -export class TagsService { +export class Tags { constructor(private readonly http: HttpClient) {} getAll(): Observable { diff --git a/src/app/features/profile/components/follow-button.component.ts b/src/app/features/profile/components/follow-button.ts similarity index 81% rename from src/app/features/profile/components/follow-button.component.ts rename to src/app/features/profile/components/follow-button.ts index 6a30346e4..68c1bc02c 100644 --- a/src/app/features/profile/components/follow-button.component.ts +++ b/src/app/features/profile/components/follow-button.ts @@ -11,9 +11,9 @@ import { import { Router } from '@angular/router'; import { switchMap } from 'rxjs/operators'; import { EMPTY } from 'rxjs'; -import { ProfileService } from '../services/profile.service'; -import { UserService } from '../../../core/auth/services/user.service'; -import { Profile } from '../models/profile.model'; +import { ProfileDataAccess } from '../services/profile-data-access'; +import { UserAuth } from '../../../core/auth/services/user-auth'; +import { ProfileModel } from '../models/profile.model'; import { NgClass } from '@angular/common'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -37,16 +37,16 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class FollowButtonComponent { - @Input() profile!: Profile; - @Output() toggle = new EventEmitter(); +export class FollowButton { + @Input() profile!: ProfileModel; + @Output() toggle = new EventEmitter(); isSubmitting = signal(false); destroyRef = inject(DestroyRef); constructor( - private readonly profileService: ProfileService, + private readonly profileService: ProfileDataAccess, private readonly router: Router, - private readonly userService: UserService, + private readonly userService: UserAuth, ) {} toggleFollowing(): void { diff --git a/src/app/features/profile/components/profile-articles.component.ts b/src/app/features/profile/components/profile-articles.ts similarity index 70% rename from src/app/features/profile/components/profile-articles.component.ts rename to src/app/features/profile/components/profile-articles.ts index 453ec2364..9153f7c9d 100644 --- a/src/app/features/profile/components/profile-articles.component.ts +++ b/src/app/features/profile/components/profile-articles.ts @@ -1,8 +1,8 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { ArticleListComponent } from '../../article/components/article-list.component'; -import { ProfileService } from '../services/profile.service'; -import { Profile } from '../models/profile.model'; +import { ArticleList } from '../../article/components/article-list'; +import { ProfileDataAccess } from '../services/profile-data-access'; +import { ProfileModel } from '../models/profile.model'; import { ArticleListConfig } from '../../article/models/article-list-config.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -11,17 +11,17 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; template: `@if (articlesConfig()) { }`, - imports: [ArticleListComponent], + imports: [ArticleList], changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class ProfileArticlesComponent implements OnInit { - profile = signal(null); +export default class ProfileArticles implements OnInit { + profile = signal(null); articlesConfig = signal(null); destroyRef = inject(DestroyRef); constructor( private route: ActivatedRoute, - private readonly profileService: ProfileService, + private readonly profileService: ProfileDataAccess, ) {} ngOnInit(): void { @@ -29,7 +29,7 @@ export default class ProfileArticlesComponent implements OnInit { .get(this.route.snapshot.params['username']) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ - next: (profile: Profile) => { + next: (profile: ProfileModel) => { this.profile.set(profile); this.articlesConfig.set({ type: 'all', diff --git a/src/app/features/profile/components/profile-favorites.component.ts b/src/app/features/profile/components/profile-favorites.ts similarity index 70% rename from src/app/features/profile/components/profile-favorites.component.ts rename to src/app/features/profile/components/profile-favorites.ts index a48ee96f1..52a3d8db2 100644 --- a/src/app/features/profile/components/profile-favorites.component.ts +++ b/src/app/features/profile/components/profile-favorites.ts @@ -1,8 +1,8 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { ArticleListComponent } from '../../article/components/article-list.component'; -import { ProfileService } from '../services/profile.service'; -import { Profile } from '../models/profile.model'; +import { ArticleList } from '../../article/components/article-list'; +import { ProfileDataAccess } from '../services/profile-data-access'; +import { ProfileModel } from '../models/profile.model'; import { ArticleListConfig } from '../../article/models/article-list-config.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -11,17 +11,17 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; template: `@if (favoritesConfig()) { }`, - imports: [ArticleListComponent], + imports: [ArticleList], changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class ProfileFavoritesComponent implements OnInit { - profile = signal(null); +export default class ProfileFavorites implements OnInit { + profile = signal(null); favoritesConfig = signal(null); destroyRef = inject(DestroyRef); constructor( private route: ActivatedRoute, - private readonly profileService: ProfileService, + private readonly profileService: ProfileDataAccess, ) {} ngOnInit() { @@ -29,7 +29,7 @@ export default class ProfileFavoritesComponent implements OnInit { .get(this.route.parent?.snapshot.params['username']) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ - next: (profile: Profile) => { + next: (profile: ProfileModel) => { this.profile.set(profile); this.favoritesConfig.set({ type: 'all', diff --git a/src/app/features/profile/models/profile.model.ts b/src/app/features/profile/models/profile.model.ts index 3ff457e14..a2378a8e8 100644 --- a/src/app/features/profile/models/profile.model.ts +++ b/src/app/features/profile/models/profile.model.ts @@ -1,4 +1,4 @@ -export interface Profile { +export interface ProfileModel { username: string; bio: string; image: string; diff --git a/src/app/features/profile/pages/profile/profile.component.html b/src/app/features/profile/pages/profile/profile.html similarity index 100% rename from src/app/features/profile/pages/profile/profile.component.html rename to src/app/features/profile/pages/profile/profile.html diff --git a/src/app/features/profile/pages/profile/profile.component.ts b/src/app/features/profile/pages/profile/profile.ts similarity index 65% rename from src/app/features/profile/pages/profile/profile.component.ts rename to src/app/features/profile/pages/profile/profile.ts index af4b5fde6..717d742d6 100644 --- a/src/app/features/profile/pages/profile/profile.component.ts +++ b/src/app/features/profile/pages/profile/profile.ts @@ -2,28 +2,28 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal import { ActivatedRoute, Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'; import { catchError, switchMap } from 'rxjs/operators'; import { combineLatest, of, throwError } from 'rxjs'; -import { UserService } from '../../../../core/auth/services/user.service'; -import { Profile } from '../../models/profile.model'; -import { ProfileService } from '../../services/profile.service'; +import { UserAuth } from '../../../../core/auth/services/user-auth'; +import { ProfileModel } from '../../models/profile.model'; +import { ProfileDataAccess } from '../../services/profile-data-access'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { FollowButtonComponent } from '../../components/follow-button.component'; +import { FollowButton } from '../../components/follow-button'; @Component({ selector: 'app-profile-page', - templateUrl: './profile.component.html', - imports: [FollowButtonComponent, RouterLink, RouterLinkActive, RouterOutlet, FollowButtonComponent], + templateUrl: './profile.html', + imports: [FollowButton, RouterLink, RouterLinkActive, RouterOutlet, FollowButton], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProfileComponent implements OnInit { - profile = signal(null!); +export class Profile implements OnInit { + profile = signal(null!); isUser = signal(false); destroyRef = inject(DestroyRef); constructor( private readonly route: ActivatedRoute, private readonly router: Router, - private readonly userService: UserService, - private readonly profileService: ProfileService, + private readonly userService: UserAuth, + private readonly profileService: ProfileDataAccess, ) {} ngOnInit() { @@ -32,6 +32,7 @@ export class ProfileComponent implements OnInit { .pipe( catchError(error => { void this.router.navigate(['/']); + return throwError(() => error); }), switchMap(profile => { @@ -45,7 +46,7 @@ export class ProfileComponent implements OnInit { }); } - onToggleFollowing(profile: Profile) { + onToggleFollowing(profile: ProfileModel) { this.profile.set(profile); } } diff --git a/src/app/features/profile/profile.routes.ts b/src/app/features/profile/profile.routes.ts index 4687b5611..de8c9b82c 100644 --- a/src/app/features/profile/profile.routes.ts +++ b/src/app/features/profile/profile.routes.ts @@ -1,5 +1,5 @@ import { Routes } from '@angular/router'; -import { ProfileComponent } from './pages/profile/profile.component'; +import { Profile } from './pages/profile/profile'; const routes: Routes = [ { @@ -7,15 +7,15 @@ const routes: Routes = [ children: [ { path: ':username', - component: ProfileComponent, + component: Profile, children: [ { path: '', - loadComponent: () => import('./components/profile-articles.component'), + loadComponent: () => import('./components/profile-articles'), }, { path: 'favorites', - loadComponent: () => import('./components/profile-favorites.component'), + loadComponent: () => import('./components/profile-favorites'), }, ], }, diff --git a/src/app/features/profile/services/profile.service.spec.ts b/src/app/features/profile/services/profile-data-access.spec.ts similarity index 97% rename from src/app/features/profile/services/profile.service.spec.ts rename to src/app/features/profile/services/profile-data-access.spec.ts index a20bbd6fa..2c848f2b2 100644 --- a/src/app/features/profile/services/profile.service.spec.ts +++ b/src/app/features/profile/services/profile-data-access.spec.ts @@ -5,18 +5,18 @@ import { TestBed, getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { firstValueFrom } from 'rxjs'; -import { ProfileService } from './profile.service'; -import { Profile } from '../models/profile.model'; +import { ProfileDataAccess } from './profile-data-access'; +import { ProfileModel } from '../models/profile.model'; -describe('ProfileService', () => { +describe('ProfileDataAccess', () => { beforeAll(() => { getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); }); - let service: ProfileService; + let service: ProfileDataAccess; let httpMock: HttpTestingController; - const mockProfile: Profile = { + const mockProfile: ProfileModel = { username: 'testuser', bio: 'Test bio', image: 'https://example.com/avatar.jpg', @@ -26,10 +26,10 @@ describe('ProfileService', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [ProfileService], + providers: [ProfileDataAccess], }); - service = TestBed.inject(ProfileService); + service = TestBed.inject(ProfileDataAccess); httpMock = TestBed.inject(HttpTestingController); }); diff --git a/src/app/features/profile/services/profile-data-access.ts b/src/app/features/profile/services/profile-data-access.ts new file mode 100644 index 000000000..7b5a06157 --- /dev/null +++ b/src/app/features/profile/services/profile-data-access.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, shareReplay } from 'rxjs/operators'; +import { ProfileModel } from '../models/profile.model'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ providedIn: 'root' }) +export class ProfileDataAccess { + constructor(private readonly http: HttpClient) {} + + get(username: string): Observable { + return this.http.get<{ profile: ProfileModel }>('/profiles/' + username).pipe( + map((data: { profile: ProfileModel }) => data.profile), + shareReplay(1), + ); + } + + follow(username: string): Observable { + return this.http + .post<{ profile: ProfileModel }>('/profiles/' + username + '/follow', {}) + .pipe(map((data: { profile: ProfileModel }) => data.profile)); + } + + unfollow(username: string): Observable { + return this.http + .delete<{ profile: ProfileModel }>('/profiles/' + username + '/follow') + .pipe(map((data: { profile: ProfileModel }) => data.profile)); + } +} diff --git a/src/app/features/profile/services/profile.service.ts b/src/app/features/profile/services/profile.service.ts deleted file mode 100644 index 5cbab1a0b..000000000 --- a/src/app/features/profile/services/profile.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map, shareReplay } from 'rxjs/operators'; -import { Profile } from '../models/profile.model'; -import { HttpClient } from '@angular/common/http'; - -@Injectable({ providedIn: 'root' }) -export class ProfileService { - constructor(private readonly http: HttpClient) {} - - get(username: string): Observable { - return this.http.get<{ profile: Profile }>('/profiles/' + username).pipe( - map((data: { profile: Profile }) => data.profile), - shareReplay(1), - ); - } - - follow(username: string): Observable { - return this.http - .post<{ profile: Profile }>('/profiles/' + username + '/follow', {}) - .pipe(map((data: { profile: Profile }) => data.profile)); - } - - unfollow(username: string): Observable { - return this.http - .delete<{ profile: Profile }>('/profiles/' + username + '/follow') - .pipe(map((data: { profile: Profile }) => data.profile)); - } -} diff --git a/src/app/features/settings/settings.component.html b/src/app/features/settings/settings.html similarity index 100% rename from src/app/features/settings/settings.component.html rename to src/app/features/settings/settings.html diff --git a/src/app/features/settings/settings.component.ts b/src/app/features/settings/settings.ts similarity index 83% rename from src/app/features/settings/settings.component.ts rename to src/app/features/settings/settings.ts index ef9e2e7af..39b97ccb4 100644 --- a/src/app/features/settings/settings.component.ts +++ b/src/app/features/settings/settings.ts @@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, signal import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { Router } from '@angular/router'; import { User } from '../../core/auth/user.model'; -import { UserService } from '../../core/auth/services/user.service'; -import { ListErrorsComponent } from '../../shared/components/list-errors.component'; +import { UserAuth } from '../../core/auth/services/user-auth'; +import { ListErrors } from '../../shared/components/list-errors'; import { Errors } from '../../core/models/errors.model'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -17,11 +17,11 @@ interface SettingsForm { @Component({ selector: 'app-settings-page', - templateUrl: './settings.component.html', - imports: [ListErrorsComponent, ReactiveFormsModule], + templateUrl: './settings.html', + imports: [ListErrors, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class SettingsComponent implements OnInit { +export default class Settings implements OnInit { user!: User; settingsForm = new FormGroup({ image: new FormControl('', { nonNullable: true }), @@ -39,7 +39,7 @@ export default class SettingsComponent implements OnInit { constructor( private readonly router: Router, - private readonly userService: UserService, + private readonly userService: UserAuth, ) {} ngOnInit(): void { diff --git a/src/app/shared/components/list-errors.component.html b/src/app/shared/components/list-errors.html similarity index 100% rename from src/app/shared/components/list-errors.component.html rename to src/app/shared/components/list-errors.html diff --git a/src/app/shared/components/list-errors.component.ts b/src/app/shared/components/list-errors.ts similarity index 84% rename from src/app/shared/components/list-errors.component.ts rename to src/app/shared/components/list-errors.ts index ba3390eb0..5f6458001 100644 --- a/src/app/shared/components/list-errors.component.ts +++ b/src/app/shared/components/list-errors.ts @@ -3,10 +3,10 @@ import { Errors } from '../../core/models/errors.model'; @Component({ selector: 'app-list-errors', - templateUrl: './list-errors.component.html', + templateUrl: './list-errors.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ListErrorsComponent { +export class ListErrors { errorList: string[] = []; @Input() set errors(errorList: Errors | null) { diff --git a/src/app/shared/pipes/markdown.pipe.ts b/src/app/shared/pipes/markdown-pipe.ts similarity index 100% rename from src/app/shared/pipes/markdown.pipe.ts rename to src/app/shared/pipes/markdown-pipe.ts diff --git a/src/main.ts b/src/main.ts index 2896ea750..188f7abca 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser'; -import { AppComponent } from './app/app.component'; +import { App } from './app/app'; import { appConfig } from './app/app.config'; -bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)); +bootstrapApplication(App, appConfig).catch(err => console.error(err));