Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/ui/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter, Router, withComponentInputBinding } from '@angular/router';
import { AppRouter } from 'core/fw-extensions/app-router';
import { authInterceptor } from 'core/interceptors/auth/auth.interceptor';
import { errorInterceptor } from 'core/interceptors/error/error.interceptor';
import { appRoutes } from './app.routes';
import { errorInterceptor } from './core/error/error-interceptor';
import { AppRouter } from './fw-extensions/app-router';
import { authInterceptor } from './interceptors/auth.interceptor';

export const appConfig: ApplicationConfig = {
providers: [
provideRouter(appRoutes, withComponentInputBinding()),
provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
provideHttpClient(withInterceptors([errorInterceptor, authInterceptor])),
provideZonelessChangeDetection(),
{
provide: Router,
Expand Down
10 changes: 5 additions & 5 deletions apps/ui/src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { Route } from '@angular/router';
import { authGuard } from 'guards/auth.guard';
import { authGuard } from 'core/guards/auth/auth.guard';

export const appRoutes: Route[] = [
{
path: 'about',
loadComponent: () => import('./components/about/about.component').then((c) => c.AboutComponent),
loadComponent: () => import('./features/about/about.component').then((c) => c.AboutComponent),
},
{
path: 'users',
loadComponent: () => import('./components/gh-users/gh-users.component').then((c) => c.GhUsersComponent),
loadComponent: () => import('./features/gh/pages/gh-users/gh-users.component').then((c) => c.GhUsersComponent),
canActivate: [authGuard],
},
{
path: 'login',
loadComponent: () => import('./components/login/login.component').then((c) => c.LoginComponent),
loadComponent: () => import('./features/auth/pages/login/login.component').then((c) => c.LoginComponent),
},
{
path: 'token-expired',
loadComponent: () => import('./components/token-expired/token-expired.component').then((c) => c.TokenExpiredComponent),
loadComponent: () => import('./features/auth/pages/token-expired/token-expired.component').then((c) => c.TokenExpiredComponent),
outlet: 'modal',
},
];
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, input } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { testSetup } from 'utils/test/setup';
import { testSetup } from 'core/utils/test/setup';
import { describe, expect, test, vi } from 'vitest';
import { LoaderDirective } from './loader.directive';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { Locator, page } from '@vitest/browser/context';
import { testSetup } from 'utils/test/setup';
import { testSetup } from 'core/utils/test/setup';
import { describe, expect, test, vi } from 'vitest';
import { TooltipTriggerDirective } from './tooltip-trigger.directive';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { inject } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { AppRouter } from 'fw-extensions/app-router';
import { AuthService } from 'services/auth.service';
import { AppRouter } from 'core/fw-extensions/app-router';
import { AuthService } from '../../../features/auth/services/auth.service';

export const authGuard: CanActivateFn = async () => {
const authService = inject(AuthService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { AppRouter } from 'fw-extensions/app-router';
import { AppRouter } from 'core/fw-extensions/app-router';
import { firstValueFrom, of } from 'rxjs';
import { AuthService } from 'services/auth.service';
import { PUBLIC_API, REFRESH_API } from 'utils/api';
import { AuthService } from '../../../features/auth/services/auth.service';
import { describe, expect, test, vi } from 'vitest';
import { PUBLIC_API, REFRESH_API } from '../../utils/api';
import { authInterceptor } from './auth.interceptor';

/// Tests for authInterceptor behavior, including token attachment and refresh retry.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { HttpErrorResponse, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
import { inject } from '@angular/core';
import { AppRouter } from 'fw-extensions/app-router';
import { AppRouter } from 'core/fw-extensions/app-router';
import { catchError, from, switchMap, tap, throwError } from 'rxjs';
import { AuthService } from 'services/auth.service';
import { IS_PUBLIC_API, IS_REFRESH_API } from 'utils/api';
import { AuthService } from '../../../features/auth/services/auth.service';
import { IS_PUBLIC_API, IS_REFRESH_API } from '../../utils/api';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
if (req.context.get(IS_PUBLIC_API)) {
Expand All @@ -19,9 +19,7 @@ export const authInterceptor: HttpInterceptorFn = (req, next) => {
console.error('*** authInterceptor error = ', error);
if (error.status === 401) {
if (req.context.get(IS_REFRESH_API)) {
return from(router.navigateToTokenExpired()).pipe(
switchMap(() => throwError(() => new Error(error.message))),
);
return from(router.navigateToTokenExpired()).pipe(switchMap(() => throwError(() => new Error(error.message))));
}
return authService.refresh(authService.refreshToken).pipe(
tap(() => authService.saveCredentials()),
Expand All @@ -43,4 +41,3 @@ const addToken = (rquest: HttpRequest<unknown>, token: string) => {
headers: rquest.headers.set('Authorization', `Bearer ${token}`),
});
};

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpInterceptorFn } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { errorInterceptor } from './error-interceptor';
import { errorInterceptor } from './error.interceptor';

// TODO: Add tests for errorInterceptor functionality

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Injectable, inject } from "@angular/core";
import { AppStore } from '../store/app.store';
import { Injectable, inject } from '@angular/core';
import { AppStore } from '../../store/app.store';

@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class StoreService {
readonly #store = inject(AppStore);
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TestBed } from '@angular/core/testing';
import { testSetup } from 'utils/test/setup';
import { testSetup } from 'core/utils/test/setup';
import { describe, expect, test } from 'vitest';
import { Locator, page } from 'vitest/browser';
import { LoginComponent } from './login.component';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
import { Component, inject, OnInit, signal } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { email, form, FormField, required, submit } from '@angular/forms/signals';
import { AppRouter } from 'fw-extensions/app-router';
import { AppRouter } from 'core/fw-extensions/app-router';
import { firstValueFrom } from 'rxjs';
import { AuthService } from 'services/auth.service';
import { AuthService } from '../../services/auth.service';

type LoginData = {
email: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, ElementRef, inject, OnInit, viewChild } from '@angular/core';
import * as bootstrap from 'bootstrap';
import { AppRouter } from 'fw-extensions/app-router';
import { removeBootstrapModals } from 'utils/dom';
import { AppRouter } from 'core/fw-extensions/app-router';
import { removeBootstrapModals } from 'core/utils/dom';

@Component({
selector: 'gh-token-expired',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { computed, inject, Injectable, signal } from '@angular/core';
import { AuthKeys } from '@gh/shared/models';
import { loggedMethod } from '@gh/shared/utils';
import { StoreService } from 'core/services/store/store.service';
import { publicGet, publicPost, refreshPost } from 'core/utils/api';
import { CookieService } from 'ngx-cookie-service';
import { catchError, of, tap } from 'rxjs';
import { StoreService } from 'services/store.service';
import { publicGet, publicPost, refreshPost } from 'utils/api';

type Credentials = {
accessToken: string;
Expand Down Expand Up @@ -34,7 +34,7 @@ export class AuthService {
get credentials() {
return {
accessToken: this.#cookieService.get(AuthKeys.AccessToken),
refreshToken: this.#cookieService.get(AuthKeys.RefreshToken)
refreshToken: this.#cookieService.get(AuthKeys.RefreshToken),
} as Credentials;
}

Expand Down Expand Up @@ -72,25 +72,24 @@ export class AuthService {

this.#error.set(undefined);

return publicPost(this.#http, url, credentials)
.pipe(
catchError((error) => {
this.#error.set(error);
if (error instanceof HttpErrorResponse) {
console.error('http error:', error);
} else {
console.error('error:', error);
}
this.clearCredentials();

return of(null);
}),
tap(() => {
if (this.accessToken && this.refreshToken) {
this.saveCredentials();
}
})
);
return publicPost(this.#http, url, credentials).pipe(
catchError((error) => {
this.#error.set(error);
if (error instanceof HttpErrorResponse) {
console.error('http error:', error);
} else {
console.error('error:', error);
}
this.clearCredentials();

return of(null);
}),
tap(() => {
if (this.accessToken && this.refreshToken) {
this.saveCredentials();
}
}),
);
}

@loggedMethod()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, ElementRef, OnInit, inject, input, signal, viewChild } from '@angular/core';
import { GhFullUser, GhRepoContributor, GhUserRepo } from '@gh/shared/models';
import { firstValueFrom, map } from 'rxjs';
import { GhService } from 'services/gh.service';
import { GhService } from '../../services/gh.service';

@Component({
selector: 'gh-repo-list-item',
Expand Down Expand Up @@ -35,22 +35,19 @@ export class GhRepoListItemComponent implements OnInit {
}

async #getRepo() {
await firstValueFrom(this.#ghService.getRepo(this.repo().owner.login, this.repo().name))
.then((response) => this.parentRepo.set(response.parent));
await firstValueFrom(this.#ghService.getRepo(this.repo().owner.login, this.repo().name)).then((response) => this.parentRepo.set(response.parent));
}

async #getContributors() {
await firstValueFrom(this.#ghService.getRepoContributors(this.repo().owner.login, this.repo().name)
.pipe(map((response) => response.filter((c) => c.login !== this.repo().owner.login))))
.then((response) => this.contributors.set(response),
await firstValueFrom(this.#ghService.getRepoContributors(this.repo().owner.login, this.repo().name).pipe(map((response) => response.filter((c) => c.login !== this.repo().owner.login)))).then(
(response) => this.contributors.set(response),
);
}

async #getLanguages() {
await firstValueFrom(this.#ghService.getRepoLanguages(this.repo().owner.login, this.repo().name))
.then((response) => {
this.sortedLanguages.set(Object.entries(response).sort((a, b) => b[1] - a[1]));
this.totalLanguages = Object.values(response).reduce((acc, current) => acc + current, 0);
});
await firstValueFrom(this.#ghService.getRepoLanguages(this.repo().owner.login, this.repo().name)).then((response) => {
this.sortedLanguages.set(Object.entries(response).sort((a, b) => b[1] - a[1]));
this.totalLanguages = Object.values(response).reduce((acc, current) => acc + current, 0);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { provideHttpClientTesting } from '@angular/common/http/testing';
import { Component, input } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { GhFullUser, GhUserMock, GhUserRepo } from '@gh/shared/models';
import { testSetup } from 'utils/test/setup';
import { testSetup } from 'core/utils/test/setup';
import { describe, expect, test } from 'vitest';
import { GhUserReposComponent } from '../gh-user-repos/gh-user-repos.component';
import { GhUserComponent } from './gh-user.component';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { CommonModule } from '@angular/common';
import { Component, ElementRef, OnInit, computed, inject, input, resource, signal, viewChild, viewChildren } from '@angular/core';
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
import { GhFullUser, GhUser, GhUserRepo } from '@gh/shared/models';
import { TooltipTriggerDirective } from 'core/directives/tooltip-trigger/tooltip-trigger.directive';
import { StoreService } from 'core/services/store/store.service';
import { firstValueFrom, tap } from 'rxjs';
import { GhUserService } from 'services/gh-user.service';
import { GhService } from 'services/gh.service';
import { StoreService } from 'services/store.service';
import { TooltipTriggerDirective } from '../../directives/tooltip-trigger/tooltip-trigger.directive';
import { GhUserService } from '../../services/gh-user.service';
import { GhService } from '../../services/gh.service';
import { GhUserReposComponent } from '../gh-user-repos/gh-user-repos.component';

@Component({
Expand All @@ -28,26 +28,23 @@ export class GhUserComponent implements OnInit {
flipped = signal(false);
flipClickedResource = resource({
params: this.flipped,
loader: ({ params }) => new Promise(() => {
if (params && !this.fullUser()) {
this.#getUser();
}
})
loader: ({ params }) =>
new Promise(() => {
if (params && !this.fullUser()) {
this.#getUser();
}
}),
});

constructor() {
toObservable(this.flipped)
.pipe(takeUntilDestroyed())
.subscribe((flipped) => this.#storeService.updateUserCards(this.user().id, flipped));
this.#userService.userCardsShowFace$
.pipe(
takeUntilDestroyed(),
)
.subscribe((show) => {
if (show && this.flipped()) {
this.flipUser();
}
});
this.#userService.userCardsShowFace$.pipe(takeUntilDestroyed()).subscribe((show) => {
if (show && this.flipped()) {
this.flipUser();
}
});
}

ngOnInit(): void {
Expand All @@ -56,8 +53,7 @@ export class GhUserComponent implements OnInit {
}
this.reposModal()?.nativeElement.addEventListener('show.bs.modal', () => {
if (!this.userRepos().length) {
firstValueFrom(this.#ghService.getAllUserRepos(this.user().login))
.then((response) => this.userRepos.set(response));
firstValueFrom(this.#ghService.getAllUserRepos(this.user().login)).then((response) => this.userRepos.set(response));
}
});
}
Expand All @@ -67,9 +63,6 @@ export class GhUserComponent implements OnInit {
}

#getUser(): void {
firstValueFrom(this.#ghService.getUser(this.user().login)
.pipe(
tap((response) => this.fullUser.set(response)),
));
firstValueFrom(this.#ghService.getUser(this.user().login).pipe(tap((response) => this.fullUser.set(response))));
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { DebugElement } from '@angular/core';
import { PageObject } from 'utils/test/page-objects';
import { PageObject } from 'core/utils/test/page-objects';
import { GhUserComponent } from './gh-user.component';

export class GhUserPageObject extends PageObject<GhUserComponent> {
getCardFront = (): DebugElement => {
return this.getDebugElementByCss('.card:not(.back)');
}
};

getCardBack = (): DebugElement => {
return this.getDebugElementByCss('.card.back');
}
};

getUserIdBadge = (): DebugElement => {
return this.getDebugElementByCss('.card:not(.back) .badge');
}
};
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { GhFullUserMock, GhUser, GhUserMock } from '@gh/shared/models';
import { createResourceMock } from 'core/utils/test/mock';
import { testSetup } from 'core/utils/test/setup';
import { of } from 'rxjs';
import { GhService } from 'services/gh.service';
import { createResourceMock } from 'utils/test/mock';
import { testSetup } from 'utils/test/setup';
import { GhService } from '../../services/gh.service';
import { describe, expect, test, vi } from 'vitest';
import { GhUsersComponent } from './gh-users.component';
import { GhUsersPageObject } from './gh-users.page-object';
Expand Down
Loading
Loading