diff --git a/angular-ngrx-scss/src/app/home/home.component.html b/angular-ngrx-scss/src/app/home/home.component.html index 56e596cff..bf1eeef87 100644 --- a/angular-ngrx-scss/src/app/home/home.component.html +++ b/angular-ngrx-scss/src/app/home/home.component.html @@ -2,7 +2,12 @@ -
-
Main content!
-
+ +
+ + +
+
diff --git a/angular-ngrx-scss/src/app/home/home.component.scss b/angular-ngrx-scss/src/app/home/home.component.scss index 3c73cead2..45dba192e 100644 --- a/angular-ngrx-scss/src/app/home/home.component.scss +++ b/angular-ngrx-scss/src/app/home/home.component.scss @@ -5,7 +5,7 @@ grid-template-rows: auto 1fr; grid-template-columns: 30% 1fr; width: 100%; - height: 100%; + height: 100vh; } .nav { @@ -14,7 +14,14 @@ } .main { + display: grid; + grid-template-columns: 24rem 1fr; grid-row: 2 / 3; grid-column: 1 / 3; background: variables.$gray100; } + +aside { + background: variables.$white; + padding: 2rem; +} diff --git a/angular-ngrx-scss/src/app/home/home.component.ts b/angular-ngrx-scss/src/app/home/home.component.ts index a53f629fd..202525fda 100644 --- a/angular-ngrx-scss/src/app/home/home.component.ts +++ b/angular-ngrx-scss/src/app/home/home.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; +import { selectUserLoginName } from '../state/user'; import { fetchUserData } from '../state/user/user.actions'; @Component({ @@ -8,6 +9,8 @@ import { fetchUserData } from '../state/user/user.actions'; styleUrls: ['./home.component.scss'], }) export class HomeComponent implements OnInit { + user$ = this.store.select(selectUserLoginName); + constructor(private store: Store) {} ngOnInit() { diff --git a/angular-ngrx-scss/src/app/home/home.module.ts b/angular-ngrx-scss/src/app/home/home.module.ts index 13aec5bc8..f040094b2 100644 --- a/angular-ngrx-scss/src/app/home/home.module.ts +++ b/angular-ngrx-scss/src/app/home/home.module.ts @@ -10,6 +10,8 @@ import { ProfileAboutComponent } from './profile/profile-about/profile-about.com import { ProfileReposComponent } from './profile/profile-repos/profile-repos.component'; import { RelativeTimePipe } from '../shared/pipes/relative-time.pipe'; import { RepoCardComponent } from '../shared/components/repo-card/repo-card.component'; +import { UserGistsComponent } from './user-gists/user-gists.component'; +import { TopRepositoriesComponent } from './top-repositories/top-repositories.component'; @NgModule({ declarations: [ @@ -22,6 +24,8 @@ import { RepoCardComponent } from '../shared/components/repo-card/repo-card.comp ProfileReposComponent, RepoCardComponent, RelativeTimePipe, + UserGistsComponent, + TopRepositoriesComponent, ], imports: [CommonModule, HomeRoutingModule], }) diff --git a/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.html b/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.html new file mode 100644 index 000000000..23015e6d4 --- /dev/null +++ b/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.html @@ -0,0 +1,19 @@ +
+

Top Repositories

+
+ + +
+ +
+ +
+
+
+
diff --git a/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.scss b/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.scss new file mode 100644 index 000000000..5919b2917 --- /dev/null +++ b/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.scss @@ -0,0 +1,35 @@ +@use '../../shared/styles/variables'; + +.top-repositories-container { + padding: 3rem; + h2 { + font-size: 18px; + line-height: 28px; + font-weight: 500; + margin-bottom: 16px; + } + .repo-container { + background-color: variables.$white; + border-radius: 0.5rem; + border-width: 1px; + } + .repo-section { + padding: 2rem 0; + padding-left: 2rem; + border-bottom: 1px solid variables.$gray300; + } + .view-all-link { + padding: 1.25rem; + background-color: variables.$gray200; + display: grid; + place-items: center; + a { + color: inherit; + font-weight: 600; + &.disabled { + color: variables.$gray300; + cursor: not-allowed; + } + } + } +} diff --git a/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.ts b/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.ts new file mode 100644 index 000000000..02e5135ed --- /dev/null +++ b/angular-ngrx-scss/src/app/home/top-repositories/top-repositories.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { selectTopRepos, selectUserLoginName } from 'src/app/state/user'; + +@Component({ + selector: 'app-top-repositories', + templateUrl: './top-repositories.component.html', + styleUrls: ['./top-repositories.component.scss'], +}) +export class TopRepositoriesComponent { + username$ = this.store.select(selectUserLoginName); + repos$ = this.store.select(selectTopRepos); + + constructor(private store: Store) {} +} diff --git a/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.html b/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.html new file mode 100644 index 000000000..be7e700d4 --- /dev/null +++ b/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.html @@ -0,0 +1,14 @@ +
+

Gists

+ + + + + +
diff --git a/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.scss b/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.scss new file mode 100644 index 000000000..2d777c505 --- /dev/null +++ b/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.scss @@ -0,0 +1,16 @@ +@use '../../shared/styles/variables'; + +.container { + padding: 2rem 0; + border-top: 1px solid variables.$gray300; + border-bottom: 1px solid variables.$gray300; + h3 { + font-weight: 600; + } + a.link { + color: variables.$black; + &:hover { + color: variables.$blue300; + } + } +} diff --git a/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.ts b/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.ts new file mode 100644 index 000000000..7525d7e17 --- /dev/null +++ b/angular-ngrx-scss/src/app/home/user-gists/user-gists.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { selectGists } from 'src/app/state/user'; + +@Component({ + selector: 'app-user-gists', + templateUrl: './user-gists.component.html', + styleUrls: ['./user-gists.component.scss'], +}) +export class UserGistsComponent { + userGists$ = this.store.select(selectGists); + + constructor(private store: Store) {} +} diff --git a/angular-ngrx-scss/src/app/shared/styles/variables.scss b/angular-ngrx-scss/src/app/shared/styles/variables.scss index d32a9fb38..68f42daf1 100644 --- a/angular-ngrx-scss/src/app/shared/styles/variables.scss +++ b/angular-ngrx-scss/src/app/shared/styles/variables.scss @@ -1,9 +1,13 @@ // colors $white: rgb(255 255 255); $gray100: rgb(243 244 246); +$gray200: rgb(249, 250, 251); +$gray300: rgb(229, 231, 235); $gray500: rgb(107 114 128); +$gray700: rgb(75 85 99); $gray900: rgb(17, 24, 39); $black: rgb(0 0 0); +$blue300: rgb(59, 130, 246); $blue600: rgb(37 99 235); // breakpoints diff --git a/angular-ngrx-scss/src/app/state/profile/profile.state.ts b/angular-ngrx-scss/src/app/state/profile/profile.state.ts index 484faa724..698a9b557 100644 --- a/angular-ngrx-scss/src/app/state/profile/profile.state.ts +++ b/angular-ngrx-scss/src/app/state/profile/profile.state.ts @@ -4,8 +4,36 @@ export interface ProfileState { user?: UserState; orgs?: UserOrgsState[]; repos?: UserReposState[]; + gists?: UserGistsState[]; } +export interface UserGistsState { + url: string; + fileName: string; +} + +interface Files { + [key: string]: { filename: string }; +} +export interface UserGist { + comments: number; + comments_url: string; + commits_url: string; + created_at: string; + forks_url: string; + git_pull_url: string; + git_push_url: string; + html_url: string; + id: string; + node_id: string; + public: boolean; + truncated: false; + updated_at: string; + url: string; + files: Files; +} + +export type UserGistsApiResponse = UserGist[]; export interface UserOrgsState { id: number; login: string; diff --git a/angular-ngrx-scss/src/app/state/user/user.actions.ts b/angular-ngrx-scss/src/app/state/user/user.actions.ts index 4fe8a5132..5cfd8f809 100644 --- a/angular-ngrx-scss/src/app/state/user/user.actions.ts +++ b/angular-ngrx-scss/src/app/state/user/user.actions.ts @@ -1,4 +1,5 @@ import { createAction, props } from '@ngrx/store'; +import { UserGistsState, UserReposState } from '../profile/profile.state'; import { UserState } from './user.state'; export const fetchUserData = createAction('[User API] User data requested'); @@ -12,3 +13,23 @@ export const fetchUserDataError = createAction( '[User API] User Data fetch unsuccessful', props<{ error: object }>(), ); + +export const fetchUserTopReposSuccess = createAction( + '[User API] User top repos successfully received', + props<{ topRepos: UserReposState[] }>(), +); + +export const fetchUserTopReposError = createAction( + '[User API] User top repos fetch unsuccessful', + props<{ error: object }>(), +); + +export const fetchUserGistsSuccess = createAction( + '[User API] User gists successfully received', + props<{ gists: UserGistsState[] }>(), +); + +export const fetchUserGistsError = createAction( + '[User API] User gists fetch unsuccessful', + props<{ error: object }>(), +); diff --git a/angular-ngrx-scss/src/app/state/user/user.effects.spec.ts b/angular-ngrx-scss/src/app/state/user/user.effects.spec.ts index a7c207b72..fad01b61d 100644 --- a/angular-ngrx-scss/src/app/state/user/user.effects.spec.ts +++ b/angular-ngrx-scss/src/app/state/user/user.effects.spec.ts @@ -2,10 +2,30 @@ import { TestBed } from '@angular/core/testing'; import { provideMockActions } from '@ngrx/effects/testing'; import { Action } from '@ngrx/store'; import { Observable, of } from 'rxjs'; -import { fetchUserData, fetchUserDataSuccess } from './user.actions'; +import { + fetchUserData, + fetchUserDataSuccess, + fetchUserGistsSuccess, + fetchUserTopReposSuccess, +} from './user.actions'; import { UserService } from '../../user/services/user.service'; import { UserEffects } from './user.effects'; import { UserState } from './user.state'; +import { UserGistsState, UserReposState } from '../profile/profile.state'; + +const USER_STATE_MOCK: UserState = { + username: 'thisdot', + avatar: '', + bio: '', + blog: '', + company: '', + email: '', + followers: 0, + following: 0, + location: '', + name: '', + twitter_username: '', +}; describe('UserEffects', () => { let actions$: Observable; @@ -17,6 +37,12 @@ describe('UserEffects', () => { getAuthenticatedUserInfo: () => { return of(); }, + getUserGists: () => { + return of(); + }, + getUserTopRepos: () => { + return of(); + }, }); TestBed.configureTestingModule({ imports: [], @@ -64,4 +90,77 @@ describe('UserEffects', () => { done(); }); }); + + it('should get the user gists from the Github API', (done) => { + actions$ = of(fetchUserDataSuccess({ userData: USER_STATE_MOCK })); + const expectedUserData: UserGistsState[] = [ + { + url: 'github.com/gists', + fileName: 'textfile1.txt', + }, + ]; + + userServiceMock.getUserGists.and.returnValue(of(expectedUserData)); + + effects.loadUserGists$.subscribe((action) => { + expect(action).toEqual( + fetchUserGistsSuccess({ gists: expectedUserData }), + ); + done(); + }); + }); + + it('should get the top repositories from the Github API', (done) => { + actions$ = of(fetchUserDataSuccess({ userData: USER_STATE_MOCK })); + const expectedUserData: UserReposState[] = [ + { + name: 'Repo-test', + description: 'This is a repo test', + language: 'TypeScript', + stargazers_count: 0, + forks_count: 0, + private: false, + updated_at: '2022-06-17T09:54:38Z', + license: null, + owner: { + login: 'thisdot', + }, + }, + { + name: 'Repo-test-2', + description: 'This is a repo test 2', + language: 'Javascript', + stargazers_count: 0, + forks_count: 0, + private: false, + updated_at: '2022-06-17T09:54:38Z', + license: null, + owner: { + login: 'thisdot', + }, + }, + { + name: 'Repo-test-3', + description: 'This is a repo test 2', + language: 'Javascript', + stargazers_count: 0, + forks_count: 0, + private: false, + updated_at: '2022-06-17T09:54:38Z', + license: null, + owner: { + login: 'thisdot', + }, + }, + ]; + + userServiceMock.getUserTopRepos.and.returnValue(of(expectedUserData)); + + effects.loadUserTopRepos$.subscribe((action) => { + expect(action).toEqual( + fetchUserTopReposSuccess({ topRepos: expectedUserData }), + ); + done(); + }); + }); }); diff --git a/angular-ngrx-scss/src/app/state/user/user.effects.ts b/angular-ngrx-scss/src/app/state/user/user.effects.ts index 720ebd794..b789596ec 100644 --- a/angular-ngrx-scss/src/app/state/user/user.effects.ts +++ b/angular-ngrx-scss/src/app/state/user/user.effects.ts @@ -7,6 +7,10 @@ import { fetchUserData, fetchUserDataError, fetchUserDataSuccess, + fetchUserGistsError, + fetchUserGistsSuccess, + fetchUserTopReposError, + fetchUserTopReposSuccess, } from './user.actions'; @Injectable() @@ -23,5 +27,29 @@ export class UserEffects { ); }); + loadUserGists$ = createEffect(() => { + return this.actions$.pipe( + ofType(fetchUserDataSuccess), + switchMap(({ userData: { username } }) => + this.userService.getUserGists(username).pipe( + map((data) => fetchUserGistsSuccess({ gists: data })), + catchError((error) => of(fetchUserGistsError({ error }))), + ), + ), + ); + }); + + loadUserTopRepos$ = createEffect(() => { + return this.actions$.pipe( + ofType(fetchUserDataSuccess), + switchMap(() => + this.userService.getUserTopRepos().pipe( + map((data) => fetchUserTopReposSuccess({ topRepos: data })), + catchError((error) => of(fetchUserTopReposError({ error }))), + ), + ), + ); + }); + constructor(public actions$: Actions, private userService: UserService) {} } diff --git a/angular-ngrx-scss/src/app/state/user/user.reducer.ts b/angular-ngrx-scss/src/app/state/user/user.reducer.ts index d5d851dfb..0632a784b 100644 --- a/angular-ngrx-scss/src/app/state/user/user.reducer.ts +++ b/angular-ngrx-scss/src/app/state/user/user.reducer.ts @@ -1,6 +1,10 @@ import { Action, createReducer, on } from '@ngrx/store'; import { UserState } from './user.state'; -import { fetchUserDataSuccess } from './user.actions'; +import { + fetchUserDataSuccess, + fetchUserGistsSuccess, + fetchUserTopReposSuccess, +} from './user.actions'; const initialUserState: UserState = { avatar: '', @@ -23,6 +27,14 @@ const reducer = createReducer( ...state, ...userData, })), + on(fetchUserGistsSuccess, (state, { gists }) => ({ + ...state, + gists, + })), + on(fetchUserTopReposSuccess, (state, { topRepos }) => ({ + ...state, + topRepos, + })), ); export function userReducer(state: UserState | undefined, action: Action) { diff --git a/angular-ngrx-scss/src/app/state/user/user.selectors.ts b/angular-ngrx-scss/src/app/state/user/user.selectors.ts index 0ae351414..a095a887d 100644 --- a/angular-ngrx-scss/src/app/state/user/user.selectors.ts +++ b/angular-ngrx-scss/src/app/state/user/user.selectors.ts @@ -13,3 +13,13 @@ export const selectUserLoginName = createSelector( selectUserState, (state: UserState) => state.username, ); + +export const selectGists = createSelector( + selectUserState, + (state: UserState) => state.gists, +); + +export const selectTopRepos = createSelector( + selectUserState, + (state: UserState) => state.topRepos, +); diff --git a/angular-ngrx-scss/src/app/state/user/user.state.ts b/angular-ngrx-scss/src/app/state/user/user.state.ts index 1fa0dc949..b042f8f5e 100644 --- a/angular-ngrx-scss/src/app/state/user/user.state.ts +++ b/angular-ngrx-scss/src/app/state/user/user.state.ts @@ -1,3 +1,5 @@ +import { UserGistsState, UserReposState } from '../profile/profile.state'; + export interface UserState { avatar: string; bio: string; @@ -10,6 +12,8 @@ export interface UserState { name: string; twitter_username: string; username: string; + topRepos?: UserReposState[]; + gists?: UserGistsState[]; } export interface UserApiResponse { diff --git a/angular-ngrx-scss/src/app/user/services/user.service.spec.ts b/angular-ngrx-scss/src/app/user/services/user.service.spec.ts index 55ee684a3..6425d5550 100644 --- a/angular-ngrx-scss/src/app/user/services/user.service.spec.ts +++ b/angular-ngrx-scss/src/app/user/services/user.service.spec.ts @@ -1,6 +1,12 @@ import { HttpClient } from '@angular/common/http'; +import { fakeAsync, tick } from '@angular/core/testing'; import { of } from 'rxjs'; -import { delay } from 'rxjs/operators'; +import { + UserGist, + UserGistsState, + UserRepo, + UserReposState, +} from 'src/app/state/profile/profile.state'; import { UserApiResponse, UserState } from 'src/app/state/user'; import { UserService } from './user.service'; @@ -17,7 +23,7 @@ describe('UserService', () => { expect(userService).toBeTruthy(); }); - it('should return user data from the GitHub API', (done) => { + it('should return user data from the GitHub API', fakeAsync(() => { const expectedResponse: UserState = { avatar: 'lindakatcodes_url', bio: '', @@ -46,13 +52,202 @@ describe('UserService', () => { twitter_username: '', }; - httpClientSpy.get.and.returnValue(of(expectedHttpResponse).pipe(delay(0))); + httpClientSpy.get.and.returnValue(of(expectedHttpResponse)); + + const result = {}; userService.getAuthenticatedUserInfo().subscribe((res) => { - expect(res).toEqual(expectedResponse); - done(); + Object.assign(result, res); }); + tick(1000); + expect(result).toEqual(expectedResponse); expect(httpClientSpy.get.calls.count()).withContext('called once').toBe(1); - }); + })); + + it('should return the top repositories from the Github API', fakeAsync(() => { + const expectedResponse: UserReposState[] = [ + { + name: 'Repo-test', + description: 'This is a repo test', + language: 'TypeScript', + stargazers_count: 0, + forks_count: 0, + private: false, + updated_at: '2022-06-17T09:54:38Z', + license: null, + owner: { + login: 'thisdot', + }, + }, + { + name: 'Repo-test-2', + description: 'This is a repo test 2', + language: 'Javascript', + stargazers_count: 0, + forks_count: 0, + private: false, + updated_at: '2022-06-17T09:54:38Z', + license: null, + owner: { + login: 'thisdot', + }, + }, + { + name: 'Repo-test-3', + description: 'This is a repo test 2', + language: 'Javascript', + stargazers_count: 0, + forks_count: 0, + private: false, + updated_at: '2022-06-17T09:54:38Z', + license: null, + owner: { + login: 'thisdot', + }, + }, + ]; + const expectedHttpResponse: Partial[] = [ + { + name: 'Repo-test', + description: 'This is a repo test', + language: 'TypeScript', + license: null, + private: false, + stargazers_count: 0, + forks_count: 0, + updated_at: '2022-06-17T09:54:38Z', + owner: { + avatar_url: 'https://avatars.githubusercontent.com/u/22839396?v=4', + events_url: 'https://api.github.com/users/thisdot/events{/privacy}', + followers_url: 'https://api.github.com/users/thisdot/followers', + following_url: + 'https://api.github.com/users/thisdot/following{/other_user}', + gists_url: 'https://api.github.com/users/thisdot/gists{/gist_id}', + gravatar_id: '', + html_url: 'https://github.com/thisdot', + id: 22839396, + login: 'thisdot', + node_id: 'MDEyOk9yZ2FuaXphdGlvbjIyODM5Mzk2', + organizations_url: 'https://api.github.com/users/thisdot/orgs', + received_events_url: + 'https://api.github.com/users/thisdot/received_events', + repos_url: 'https://api.github.com/users/thisdot/repos', + site_admin: false, + starred_url: + 'https://api.github.com/users/thisdot/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/thisdot/subscriptions', + type: 'Organization', + url: 'https://api.github.com/users/thisdot', + }, + }, + { + name: 'Repo-test-2', + description: 'This is a repo test 2', + language: 'Javascript', + license: null, + private: false, + stargazers_count: 0, + forks_count: 0, + updated_at: '2022-06-17T09:54:38Z', + owner: { + avatar_url: 'https://avatars.githubusercontent.com/u/22839396?v=4', + events_url: 'https://api.github.com/users/thisdot/events{/privacy}', + followers_url: 'https://api.github.com/users/thisdot/followers', + following_url: + 'https://api.github.com/users/thisdot/following{/other_user}', + gists_url: 'https://api.github.com/users/thisdot/gists{/gist_id}', + gravatar_id: '', + html_url: 'https://github.com/thisdot', + id: 22839396, + login: 'thisdot', + node_id: 'MDEyOk9yZ2FuaXphdGlvbjIyODM5Mzk2', + organizations_url: 'https://api.github.com/users/thisdot/orgs', + received_events_url: + 'https://api.github.com/users/thisdot/received_events', + repos_url: 'https://api.github.com/users/thisdot/repos', + site_admin: false, + starred_url: + 'https://api.github.com/users/thisdot/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/thisdot/subscriptions', + type: 'Organization', + url: 'https://api.github.com/users/thisdot', + }, + }, + { + name: 'Repo-test-3', + description: 'This is a repo test 2', + language: 'Javascript', + private: false, + stargazers_count: 0, + forks_count: 0, + updated_at: '2022-06-17T09:54:38Z', + owner: { + avatar_url: 'https://avatars.githubusercontent.com/u/22839396?v=4', + events_url: 'https://api.github.com/users/thisdot/events{/privacy}', + followers_url: 'https://api.github.com/users/thisdot/followers', + following_url: + 'https://api.github.com/users/thisdot/following{/other_user}', + gists_url: 'https://api.github.com/users/thisdot/gists{/gist_id}', + gravatar_id: '', + html_url: 'https://github.com/thisdot', + id: 22839396, + login: 'thisdot', + node_id: 'MDEyOk9yZ2FuaXphdGlvbjIyODM5Mzk2', + organizations_url: 'https://api.github.com/users/thisdot/orgs', + received_events_url: + 'https://api.github.com/users/thisdot/received_events', + repos_url: 'https://api.github.com/users/thisdot/repos', + site_admin: false, + starred_url: + 'https://api.github.com/users/thisdot/starred{/owner}{/repo}', + subscriptions_url: + 'https://api.github.com/users/thisdot/subscriptions', + type: 'Organization', + url: 'https://api.github.com/users/thisdot', + }, + }, + ]; + httpClientSpy.get.and.returnValue(of(expectedHttpResponse)); + + const result: UserReposState[] = []; + + userService.getUserTopRepos().subscribe((res) => { + Object.assign(result, res); + }); + tick(1000); + + expect(result).toEqual(expectedResponse); + expect(httpClientSpy.get.calls.count()).withContext('called once').toBe(1); + })); + + it('should return the gists from the user', fakeAsync(() => { + const username = 'thisDot'; + const expectedResponse: UserGistsState[] = [ + { + url: 'github.com/gists', + fileName: 'textfile1.txt', + }, + ]; + + const expectedHttpResponse: Partial[] = [ + { + html_url: 'github.com/gists', + files: { 'textfile1.txt': { filename: 'textfile1.txt' } }, + }, + ]; + + httpClientSpy.get.and.returnValue(of(expectedHttpResponse)); + + const result: UserGistsState[] = []; + + userService.getUserGists(username).subscribe((res) => { + Object.assign(result, res); + }); + tick(1000); + + expect(result).toEqual(expectedResponse); + })); }); diff --git a/angular-ngrx-scss/src/app/user/services/user.service.ts b/angular-ngrx-scss/src/app/user/services/user.service.ts index 283d43ed1..46bb615e1 100644 --- a/angular-ngrx-scss/src/app/user/services/user.service.ts +++ b/angular-ngrx-scss/src/app/user/services/user.service.ts @@ -1,7 +1,10 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { map, Observable } from 'rxjs'; import { + UserGist, + UserGistsApiResponse, + UserGistsState, UserOrgsApiResponse, UserOrgsState, UserReposApiResponse, @@ -105,4 +108,60 @@ export class UserService { ), ); } + + getUserTopRepos(): Observable { + const defaultParams = { + sort: 'updated', + per_page: 20, + }; + + const url = `${environment.githubUrl}/user/repos`; + + return this.http + .get(url, { + params: new HttpParams({ + fromObject: { ...Object.assign(defaultParams) }, + }), + }) + .pipe( + map((data) => + data.map((repo) => ({ + name: repo.name, + description: repo.description, + language: repo.language, + stargazers_count: repo.stargazers_count, + forks_count: repo.forks_count, + private: repo.private, + updated_at: repo.updated_at, + license: repo.license + ? { + key: repo.license.key, + name: repo.license.name, + spdx_id: repo.license.spdx_id, + url: repo.license.url, + node_id: repo.license.node_id, + } + : null, + owner: { + login: repo.owner.login, + }, + })), + ), + ); + } + + getUserGists(username: string): Observable { + const url = `${environment.githubUrl}/users/${encodeURIComponent( + username, + )}/gists`; + + return this.http.get(url).pipe( + map((data) => + data.map((gist: UserGist) => ({ + url: gist.html_url, + fileName: Object.keys(gist.files)[0], + })), + ), + ); + } }