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 @@
+
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 @@
+
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],
+ })),
+ ),
+ );
+ }
}