From 408c1a616577bb1bff3dbd7d2d15c7087a82180b Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Fri, 28 Mar 2025 13:36:46 +0200 Subject: [PATCH 1/4] chore(dashboard): draft api --- .../core/services/json-api/json-api.entity.ts | 13 ++++-- .../services/json-api/json-api.service.ts | 44 ++++++++++++------- src/app/core/services/user/user.service.ts | 14 +++--- src/app/features/home/dashboard.service.ts | 25 +++++++++++ .../features/home/mappers/dashboard.mapper.ts | 41 +++++++++++++++++ .../bibliographic-contributor.entity.ts | 10 +++++ .../home/models/contributor.entity.ts | 4 -- .../features/home/models/project.entity.ts | 14 ++++-- .../models/raw-models/ProjectItem.entity.ts | 10 +++++ .../bibliographicContributorUS.entity.ts | 6 +++ .../models/raw-models/projectUS.entity.ts | 13 ++++++ .../home/models/raw-models/userUS.entity.ts | 17 +++++++ src/app/features/home/models/user.entity.ts | 7 +++ 13 files changed, 182 insertions(+), 36 deletions(-) create mode 100644 src/app/features/home/dashboard.service.ts create mode 100644 src/app/features/home/mappers/dashboard.mapper.ts create mode 100644 src/app/features/home/models/bibliographic-contributor.entity.ts delete mode 100644 src/app/features/home/models/contributor.entity.ts create mode 100644 src/app/features/home/models/raw-models/ProjectItem.entity.ts create mode 100644 src/app/features/home/models/raw-models/bibliographicContributorUS.entity.ts create mode 100644 src/app/features/home/models/raw-models/projectUS.entity.ts create mode 100644 src/app/features/home/models/raw-models/userUS.entity.ts create mode 100644 src/app/features/home/models/user.entity.ts diff --git a/src/app/core/services/json-api/json-api.entity.ts b/src/app/core/services/json-api/json-api.entity.ts index ec4610998..31e660911 100644 --- a/src/app/core/services/json-api/json-api.entity.ts +++ b/src/app/core/services/json-api/json-api.entity.ts @@ -1,8 +1,13 @@ export interface JsonApiResponse { - data: ApiData | ApiData[]; + data: T; } -export interface ApiData { - id: string | number; - attributes: T; +export interface JsonApiArrayResponse { + data: T[]; +} + +export interface ApiData { + id: string; + attributes: Attributes; + embeds: Embeds; } diff --git a/src/app/core/services/json-api/json-api.service.ts b/src/app/core/services/json-api/json-api.service.ts index 37769e667..dc81d4fe6 100644 --- a/src/app/core/services/json-api/json-api.service.ts +++ b/src/app/core/services/json-api/json-api.service.ts @@ -1,8 +1,8 @@ -import { inject, Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { map, Observable } from 'rxjs'; +import {inject, Injectable} from '@angular/core'; +import {HttpClient, HttpParams} from '@angular/common/http'; +import {map, Observable} from 'rxjs'; import { - ApiData, + ApiData, JsonApiArrayResponse, JsonApiResponse, } from '@core/services/json-api/json-api.entity'; @@ -12,19 +12,31 @@ import { export class JsonApiService { http: HttpClient = inject(HttpClient); - get(url: string): Observable { - return this.http - .get>(url) - .pipe(map((response) => (response.data as ApiData).attributes)); - } + // get(url: string): Observable { + // return this.http + // .get(url) + // .pipe(map((response) => (response.data as ApiData).attributes)); + // } + + getArray(url: string, params?: Record): Observable { + let httpParams = new HttpParams(); + + if (params) { + for (const key in params) { + const value = params[key]; + + if (Array.isArray(value)) { + value.forEach((item) => { + httpParams = httpParams.append(`${key}[]`, item); // Handles arrays + }); + } else { + httpParams = httpParams.set(key, value); + } + } + } - getArray(url: string): Observable { return this.http - .get>(url) - .pipe( - map((response) => - (response.data as ApiData[]).map((item) => item.attributes), - ), - ); + .get>(url) + .pipe(map(response => response.data as T[])); } } diff --git a/src/app/core/services/user/user.service.ts b/src/app/core/services/user/user.service.ts index a1b5c2614..7746e37c0 100644 --- a/src/app/core/services/user/user.service.ts +++ b/src/app/core/services/user/user.service.ts @@ -1,9 +1,5 @@ import { inject, Injectable } from '@angular/core'; -import { map, Observable } from 'rxjs'; import { JsonApiService } from '@core/services/json-api/json-api.service'; -import { User } from '@core/services/user/user.entity'; -import { UserUS } from '@core/services/json-api/underscore-entites/user/user-us.entity'; -import { mapUserUStoUser } from '@core/services/mappers/users/users.mapper'; @Injectable({ providedIn: 'root', @@ -11,9 +7,9 @@ import { mapUserUStoUser } from '@core/services/mappers/users/users.mapper'; export class UserService { jsonApiService = inject(JsonApiService); - getMe(): Observable { - return this.jsonApiService - .get('https://api.test.osf.io/v2/users/me') - .pipe(map((user) => mapUserUStoUser(user))); - } + // getMe(): Observable { + // return this.jsonApiService + // .get('https://api.test.osf.io/v2/users/me') + // .pipe(map((user) => mapUserUStoUser(user))); + // } } diff --git a/src/app/features/home/dashboard.service.ts b/src/app/features/home/dashboard.service.ts new file mode 100644 index 000000000..4e88542ad --- /dev/null +++ b/src/app/features/home/dashboard.service.ts @@ -0,0 +1,25 @@ +import {inject, Injectable} from '@angular/core'; +import {JsonApiService} from '@core/services/json-api/json-api.service'; +import {map, Observable} from 'rxjs'; +import {Project} from '@osf/features/home/models/project.entity'; +import {mapProjectUStoProject} from '@osf/features/home/mappers/dashboard.mapper'; +import {ProjectItem} from '@osf/features/home/models/raw-models/ProjectItem.entity'; + +@Injectable({ + providedIn: 'root', +}) +export class DashboardService { + jsonApiService = inject(JsonApiService); + + getProjects(): Observable { + const userId = 'k9p2t'; + const params = { + embed: ['bibliographic_contributors', 'parent', 'root'], + page: 1, + sort: '-last_logged', + }; + + return this.jsonApiService.getArray(`https://api.staging4.osf.io/v2/sparse/users/${userId}`, params) + .pipe(map(projects => projects.map(mapProjectUStoProject))) + } +} diff --git a/src/app/features/home/mappers/dashboard.mapper.ts b/src/app/features/home/mappers/dashboard.mapper.ts new file mode 100644 index 000000000..2f4547ee3 --- /dev/null +++ b/src/app/features/home/mappers/dashboard.mapper.ts @@ -0,0 +1,41 @@ +import {Project} from '@osf/features/home/models/project.entity'; +import {ProjectItem} from '@osf/features/home/models/raw-models/ProjectItem.entity'; +import {BibliographicContributor} from '@osf/features/home/models/bibliographic-contributor.entity'; + +export function mapProjectUStoProject(dataItem: ProjectItem): Project { + + const contributors: BibliographicContributor[] = []; + + for (const item of dataItem.embeds.bibliographic_contributors.data) { + contributors.push(({ + bibliographic: item.attributes.bibliographic, + id: item.id, + index: item.attributes.index, + permission: item.attributes.permission, + unregisteredContributor: item.attributes.unregistered_contributor, + users: { + familyName: item.embeds.users.data.attributes.family_name, + fullName: item.embeds.users.data.attributes.full_name, + givenName: item.embeds.users.data.attributes.given_name, + locale: item.embeds.users.data.attributes.locale, + timezone: item.embeds.users.data.attributes.timezone + } + } as BibliographicContributor)) + } + + return { + id: dataItem.id, + title: dataItem.attributes.title, + description: dataItem.attributes.description, + category: dataItem.attributes.category, + dateCreated: new Date(dataItem.attributes.date_created), + dateModified: new Date(dataItem.attributes.date_created), + fork: dataItem.attributes.fork, + tags: dataItem.attributes.tags, + currentUserPermissions: dataItem.attributes.current_user_permissions, + currentUserIsContributor: dataItem.attributes.current_user_is_contributor, + currentUserIsContributorOrGroupMember: dataItem.attributes.current_user_is_contributor_or_group_member, + public: dataItem.attributes.public, + bibliographicContributors: contributors + } +} diff --git a/src/app/features/home/models/bibliographic-contributor.entity.ts b/src/app/features/home/models/bibliographic-contributor.entity.ts new file mode 100644 index 000000000..5055fae4b --- /dev/null +++ b/src/app/features/home/models/bibliographic-contributor.entity.ts @@ -0,0 +1,10 @@ +import {User} from '@core/services/user/user.entity'; + +export interface BibliographicContributor { + id: string; + bibliographic: boolean; + index: number; + permission: string; + unregisteredContributor: boolean; + users: User; +} diff --git a/src/app/features/home/models/contributor.entity.ts b/src/app/features/home/models/contributor.entity.ts deleted file mode 100644 index a1c4f35c7..000000000 --- a/src/app/features/home/models/contributor.entity.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Contributor { - id: string; - unregisteredContributor: string; -} diff --git a/src/app/features/home/models/project.entity.ts b/src/app/features/home/models/project.entity.ts index 8c0e33307..8afbf6fa1 100644 --- a/src/app/features/home/models/project.entity.ts +++ b/src/app/features/home/models/project.entity.ts @@ -1,9 +1,17 @@ -import { Contributor } from '@osf/features/home/models/contributor.entity'; +import {BibliographicContributor} from '@osf/features/home/models/bibliographic-contributor.entity'; export interface Project { id: string; title: string; + description: string; + category: string; + dateCreated: Date; dateModified: Date; - bibliographicContributors: Contributor[]; - links: null; // needs to be researched, there is a big hierarchy + fork: boolean; + tags: string[]; + currentUserPermissions: string[]; + currentUserIsContributor: boolean; + currentUserIsContributorOrGroupMember: boolean; + public: boolean; + bibliographicContributors: BibliographicContributor[]; } diff --git a/src/app/features/home/models/raw-models/ProjectItem.entity.ts b/src/app/features/home/models/raw-models/ProjectItem.entity.ts new file mode 100644 index 000000000..af1f3a2c8 --- /dev/null +++ b/src/app/features/home/models/raw-models/ProjectItem.entity.ts @@ -0,0 +1,10 @@ +import {ProjectUS} from '@osf/features/home/models/raw-models/projectUS.entity'; +import {ApiData, JsonApiArrayResponse, JsonApiResponse} from '@core/services/json-api/json-api.entity'; +import {BibliographicContributorUS} from '@osf/features/home/models/raw-models/bibliographicContributorUS.entity'; +import {UserUS} from '@osf/features/home/models/raw-models/userUS.entity'; + +export type ProjectItem = ApiData> + }>> +}> diff --git a/src/app/features/home/models/raw-models/bibliographicContributorUS.entity.ts b/src/app/features/home/models/raw-models/bibliographicContributorUS.entity.ts new file mode 100644 index 000000000..67665c316 --- /dev/null +++ b/src/app/features/home/models/raw-models/bibliographicContributorUS.entity.ts @@ -0,0 +1,6 @@ +export interface BibliographicContributorUS { + bibliographic: boolean; + index: number; + permission: string; + unregistered_contributor: boolean; +} diff --git a/src/app/features/home/models/raw-models/projectUS.entity.ts b/src/app/features/home/models/raw-models/projectUS.entity.ts new file mode 100644 index 000000000..bdf4ff92c --- /dev/null +++ b/src/app/features/home/models/raw-models/projectUS.entity.ts @@ -0,0 +1,13 @@ +export interface ProjectUS { + title: string; + description: string; + category: string, + date_created: string, + date_modified: string, + fork: boolean, + tags: string[], + current_user_permissions: string[], + current_user_is_contributor: boolean, + current_user_is_contributor_or_group_member: boolean, + public: boolean +} diff --git a/src/app/features/home/models/raw-models/userUS.entity.ts b/src/app/features/home/models/raw-models/userUS.entity.ts new file mode 100644 index 000000000..3147de098 --- /dev/null +++ b/src/app/features/home/models/raw-models/userUS.entity.ts @@ -0,0 +1,17 @@ +export interface UserUS { + accepted_terms_of_service: boolean; + active: boolean; + allow_indexing: boolean; + can_view_reviews: []; + date_registered: string; + education: []; + employment: []; + family_name: string; + full_name: string; + given_name: string; + locale: string; + middle_names: string; + social: object; + suffix: string; + timezone: string; +} diff --git a/src/app/features/home/models/user.entity.ts b/src/app/features/home/models/user.entity.ts new file mode 100644 index 000000000..435071d62 --- /dev/null +++ b/src/app/features/home/models/user.entity.ts @@ -0,0 +1,7 @@ +export interface User { + familyName: string; + fullName: string; + givenName: string; + locale: string; + timezone: string; +} From 488503c40d5b155c3f22b299b86fe43ee02c1628 Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Fri, 28 Mar 2025 17:58:20 +0200 Subject: [PATCH 2/4] fix(dashboard): fixes for api --- .../services/json-api/json-api.service.ts | 48 +++-- src/app/core/services/user/user.entity.ts | 1 + src/app/features/home/dashboard.service.ts | 20 +- src/app/features/home/data.ts | 195 ------------------ src/app/features/home/home.component.ts | 24 ++- 5 files changed, 64 insertions(+), 224 deletions(-) diff --git a/src/app/core/services/json-api/json-api.service.ts b/src/app/core/services/json-api/json-api.service.ts index dc81d4fe6..26bf5162f 100644 --- a/src/app/core/services/json-api/json-api.service.ts +++ b/src/app/core/services/json-api/json-api.service.ts @@ -1,8 +1,8 @@ -import {inject, Injectable} from '@angular/core'; -import {HttpClient, HttpParams} from '@angular/common/http'; -import {map, Observable} from 'rxjs'; +import { inject, Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; +import { map, Observable } from 'rxjs'; import { - ApiData, JsonApiArrayResponse, + JsonApiArrayResponse, JsonApiResponse, } from '@core/services/json-api/json-api.entity'; @@ -12,13 +12,29 @@ import { export class JsonApiService { http: HttpClient = inject(HttpClient); - // get(url: string): Observable { - // return this.http - // .get(url) - // .pipe(map((response) => (response.data as ApiData).attributes)); - // } + get(url: string, params?: Record): Observable { + let httpParams = new HttpParams(); + + if (params) { + for (const key in params) { + const value = params[key]; + + if (Array.isArray(value)) { + value.forEach((item) => { + httpParams = httpParams.append(`${key}[]`, item); // Handles arrays + }); + } else { + httpParams = httpParams.set(key, value as string); + } + } + } - getArray(url: string, params?: Record): Observable { + return this.http + .get>(url) + .pipe(map((response) => response.data)); + } + + getArray(url: string, params?: Record): Observable { let httpParams = new HttpParams(); if (params) { @@ -30,13 +46,19 @@ export class JsonApiService { httpParams = httpParams.append(`${key}[]`, item); // Handles arrays }); } else { - httpParams = httpParams.set(key, value); + httpParams = httpParams.set(key, value as string); } } } + const headers = new HttpHeaders({ + Authorization: `Bearer UlO9O9GNKgVzJD7pUeY53jiQTKJ4U2znXVWNvh0KZQruoENuILx0IIYf9LoDz7Duq72EIm`, + }); + return this.http - .get>(url) - .pipe(map(response => response.data as T[])); + .get< + JsonApiArrayResponse + >(url, { params: httpParams, headers: headers }) + .pipe(map((response) => response.data)); } } diff --git a/src/app/core/services/user/user.entity.ts b/src/app/core/services/user/user.entity.ts index 6ff2e9419..04fe10f33 100644 --- a/src/app/core/services/user/user.entity.ts +++ b/src/app/core/services/user/user.entity.ts @@ -1,4 +1,5 @@ export interface User { fullName: string; givenName: string; + familyName: string; } diff --git a/src/app/features/home/dashboard.service.ts b/src/app/features/home/dashboard.service.ts index 4e88542ad..dc651efdc 100644 --- a/src/app/features/home/dashboard.service.ts +++ b/src/app/features/home/dashboard.service.ts @@ -1,9 +1,9 @@ -import {inject, Injectable} from '@angular/core'; -import {JsonApiService} from '@core/services/json-api/json-api.service'; -import {map, Observable} from 'rxjs'; -import {Project} from '@osf/features/home/models/project.entity'; -import {mapProjectUStoProject} from '@osf/features/home/mappers/dashboard.mapper'; -import {ProjectItem} from '@osf/features/home/models/raw-models/ProjectItem.entity'; +import { inject, Injectable } from '@angular/core'; +import { JsonApiService } from '@core/services/json-api/json-api.service'; +import { map, Observable } from 'rxjs'; +import { Project } from '@osf/features/home/models/project.entity'; +import { mapProjectUStoProject } from '@osf/features/home/mappers/dashboard.mapper'; +import { ProjectItem } from '@osf/features/home/models/raw-models/ProjectItem.entity'; @Injectable({ providedIn: 'root', @@ -19,7 +19,11 @@ export class DashboardService { sort: '-last_logged', }; - return this.jsonApiService.getArray(`https://api.staging4.osf.io/v2/sparse/users/${userId}`, params) - .pipe(map(projects => projects.map(mapProjectUStoProject))) + return this.jsonApiService + .getArray( + `https://api.staging4.osf.io/v2/sparse/users/${userId}/nodes/`, + params, + ) + .pipe(map((projects) => projects.map(mapProjectUStoProject))); } } diff --git a/src/app/features/home/data.ts b/src/app/features/home/data.ts index ef10feb7c..817af37c0 100644 --- a/src/app/features/home/data.ts +++ b/src/app/features/home/data.ts @@ -1,198 +1,3 @@ -import { Project } from '@osf/features/home/models/project.entity'; - -export const projects: Project[] = [ - { - id: '1', - title: 'Project name example', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Steger', - }, - { - id: '2', - unregisteredContributor: 'Oison', - }, - { - id: '2', - unregisteredContributor: 'Errington', - }, - ], - links: null, - }, - { - id: '2', - title: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Longsurname1', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolosit', - }, - { - id: '2', - unregisteredContributor: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', - }, - ], - links: null, - }, - { - id: '3', - title: 'Lorem ipsum dolor sit amet', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Loremipsumdolorsitam1', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolorsitamipsumdol2', - }, - ], - links: null, - }, - { - id: '4', - title: 'Project long name example Lorem ipsum dolor/', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Steger', - }, - { - id: '2', - unregisteredContributor: 'Oison', - }, - { - id: '2', - unregisteredContributor: 'Errington', - }, - ], - links: null, - }, - { - id: '5', - title: 'Project long name example /', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Steger', - }, - { - id: '2', - unregisteredContributor: 'Oison', - }, - { - id: '2', - unregisteredContributor: 'Errington', - }, - ], - links: null, - }, - { - id: '6', - title: 'Project long name example', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Steger', - }, - { - id: '2', - unregisteredContributor: 'Oison', - }, - { - id: '2', - unregisteredContributor: 'Errington', - }, - ], - links: null, - }, - { - id: '7', - title: 'Project long name example Lorem ipsum dolor sit amet', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Longsurname1', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolosit', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolorsitam', - }, - ], - links: null, - }, - { - id: '8', - title: 'Lorem ipsum dolor sit amet', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Loremipsumdolorsitam1', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolorsitamipsumdol2', - }, - ], - links: null, - }, - { - id: '9', - title: 'Project long name example Lorem ipsum dolor sit amet', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Longsurname1', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolosit', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolorsitam', - }, - ], - links: null, - }, - { - id: '10', - title: 'Lorem ipsum dolor sit amet', - dateModified: new Date(), - bibliographicContributors: [ - { - id: '1', - unregisteredContributor: 'Loremipsumdolorsitam1', - }, - { - id: '2', - unregisteredContributor: 'Loremipsumdolorsitamipsumdol2', - }, - ], - links: null, - }, -]; - export const noteworthy = [ { title: diff --git a/src/app/features/home/home.component.ts b/src/app/features/home/home.component.ts index 47a97dedb..babfb286a 100644 --- a/src/app/features/home/home.component.ts +++ b/src/app/features/home/home.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, signal } from '@angular/core'; +import { Component, computed, inject, OnInit, signal } from '@angular/core'; import { TableModule } from 'primeng/table'; import { Project } from '@osf/features/home/models/project.entity'; import { DatePipe } from '@angular/common'; @@ -8,7 +8,8 @@ import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.com import { SearchInputComponent } from '@shared/components/search-input/search-input.component'; import { IS_MEDIUM, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; -import { noteworthy, mostPopular, projects } from '@osf/features/home/data'; +import { noteworthy, mostPopular } from '@osf/features/home/data'; +import { DashboardService } from '@osf/features/home/dashboard.service'; @Component({ selector: 'osf-home', @@ -24,10 +25,11 @@ import { noteworthy, mostPopular, projects } from '@osf/features/home/data'; templateUrl: './home.component.html', styleUrl: './home.component.scss', }) -export class HomeComponent { +export class HomeComponent implements OnInit { isMedium = toSignal(inject(IS_MEDIUM)); isMobile = toSignal(inject(IS_XSMALL)); - projects: Project[] = projects; + dashboardService: DashboardService = inject(DashboardService); + projects = signal([]); noteworthy = noteworthy; mostPopular = mostPopular; @@ -35,19 +37,25 @@ export class HomeComponent { filteredProjects = computed(() => { const search = this.searchValue().toLowerCase(); - return this.projects.filter( + return this.projects().filter( (project) => project.title.toLowerCase().includes(search) || project.bibliographicContributors.some((i) => - i.unregisteredContributor.toLowerCase().includes(search), + i.users.familyName.toLowerCase().includes(search), ), ); }); getContributorsList(item: Project) { - return this.projects + return this.projects() .find((i) => i.id === item.id) - ?.bibliographicContributors.map((i) => i.unregisteredContributor) + ?.bibliographicContributors.map((i) => i.users.familyName) .join(', '); } + + ngOnInit() { + this.dashboardService.getProjects().subscribe((res) => { + this.projects.set(res); + }); + } } From c757fd6bde4db586501ec8ab622f356a3498f590 Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Wed, 2 Apr 2025 15:06:53 +0300 Subject: [PATCH 3/4] chore(dashboard): add public projects and envs --- angular.json | 8 ++++- src/app/features/home/dashboard.service.ts | 33 ++++++++++++++++++++- src/app/features/home/home.component.html | 14 +++++---- src/app/features/home/home.component.ts | 27 +++++++++++++++-- src/environments/environment.development.ts | 1 + src/environments/environment.ts | 4 +++ 6 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 src/environments/environment.development.ts create mode 100644 src/environments/environment.ts diff --git a/angular.json b/angular.json index e2abb6785..efec38eb1 100644 --- a/angular.json +++ b/angular.json @@ -58,7 +58,13 @@ "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] } }, "defaultConfiguration": "production" diff --git a/src/app/features/home/dashboard.service.ts b/src/app/features/home/dashboard.service.ts index dc651efdc..94e21caa2 100644 --- a/src/app/features/home/dashboard.service.ts +++ b/src/app/features/home/dashboard.service.ts @@ -4,6 +4,7 @@ import { map, Observable } from 'rxjs'; import { Project } from '@osf/features/home/models/project.entity'; import { mapProjectUStoProject } from '@osf/features/home/mappers/dashboard.mapper'; import { ProjectItem } from '@osf/features/home/models/raw-models/ProjectItem.entity'; +import { environment } from '../../../environments/environment'; @Injectable({ providedIn: 'root', @@ -21,7 +22,37 @@ export class DashboardService { return this.jsonApiService .getArray( - `https://api.staging4.osf.io/v2/sparse/users/${userId}/nodes/`, + `${environment.apiUrl}/sparse/users/${userId}/nodes/`, + params, + ) + .pipe(map((projects) => projects.map(mapProjectUStoProject))); + } + + getNoteworthy(): Observable { + const projectId = 'pf5z9'; + const params = { + embed: 'bibliographic_contributors', + 'page[size]': 5, + }; + + return this.jsonApiService + .getArray( + `${environment.apiUrl}/nodes/${projectId}/linked_nodes`, + params, + ) + .pipe(map((projects) => projects.map(mapProjectUStoProject))); + } + + getMostPopular(): Observable { + const projectId = 'kvw3y'; + const params = { + embed: 'bibliographic_contributors', + 'page[size]': 5, + }; + + return this.jsonApiService + .getArray( + `${environment.apiUrl}/nodes/${projectId}/linked_nodes`, params, ) .pipe(map((projects) => projects.map(mapProjectUStoProject))); diff --git a/src/app/features/home/home.component.html b/src/app/features/home/home.component.html index c3cff8964..cd48246c7 100644 --- a/src/app/features/home/home.component.html +++ b/src/app/features/home/home.component.html @@ -76,21 +76,23 @@

Discover Public Projects

New And Noteworthy

- @for (block of noteworthy; track block.title) { + @for (project of noteworthy(); track project.id) {
-

{{ block.title }}

+

{{ project.title }}


-

{{ block.authors }}

+

by {{ getNoteworthyContributorsList(project) }}

}

Most Popular

- @for (block of mostPopular; track block.title) { + @for (project of mostPopular(); track project.id) {
-

{{ block.title }}

+

{{ project.title }}


-

{{ block.authors }}

+

+ by {{ getMostPopularContributorsList(project) }} +

}
diff --git a/src/app/features/home/home.component.ts b/src/app/features/home/home.component.ts index babfb286a..e63dcbb4c 100644 --- a/src/app/features/home/home.component.ts +++ b/src/app/features/home/home.component.ts @@ -8,7 +8,6 @@ import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.com import { SearchInputComponent } from '@shared/components/search-input/search-input.component'; import { IS_MEDIUM, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; -import { noteworthy, mostPopular } from '@osf/features/home/data'; import { DashboardService } from '@osf/features/home/dashboard.service'; @Component({ @@ -30,8 +29,8 @@ export class HomeComponent implements OnInit { isMobile = toSignal(inject(IS_XSMALL)); dashboardService: DashboardService = inject(DashboardService); projects = signal([]); - noteworthy = noteworthy; - mostPopular = mostPopular; + noteworthy = signal([]); + mostPopular = signal([]); searchValue = signal(''); @@ -53,9 +52,31 @@ export class HomeComponent implements OnInit { .join(', '); } + getNoteworthyContributorsList(item: Project) { + return this.noteworthy() + .find((i) => i.id === item.id) + ?.bibliographicContributors.map((i) => i.users.familyName) + .join(', '); + } + + getMostPopularContributorsList(item: Project) { + return this.mostPopular() + .find((i) => i.id === item.id) + ?.bibliographicContributors.map((i) => i.users.familyName) + .join(', '); + } + ngOnInit() { this.dashboardService.getProjects().subscribe((res) => { this.projects.set(res); }); + + this.dashboardService.getNoteworthy().subscribe((res) => { + this.noteworthy.set(res); + }); + + this.dashboardService.getMostPopular().subscribe((res) => { + this.mostPopular.set(res); + }); } } diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts new file mode 100644 index 000000000..f274e5edf --- /dev/null +++ b/src/environments/environment.development.ts @@ -0,0 +1 @@ +export const environment = {}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 000000000..2d4be37a9 --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + production: false, + apiUrl: 'https://api.staging4.osf.io/v2', +}; From e542e575b2a7b8c143081cb8439a54bcb72b2ad1 Mon Sep 17 00:00:00 2001 From: Kyrylo Petrov Date: Fri, 4 Apr 2025 13:33:17 +0300 Subject: [PATCH 4/4] fix(home): home state --- src/app/app.config.ts | 7 ++- src/app/app.module.ts | 3 +- src/app/core/store/home/home.actions.ts | 11 +++++ src/app/core/store/home/home.model.ts | 7 +++ src/app/core/store/home/home.selectors.ts | 21 +++++++++ src/app/core/store/home/home.state.ts | 50 +++++++++++++++++++++ src/app/core/store/home/index.ts | 4 ++ src/app/features/home/home.component.ts | 37 +++++++++------ src/environments/environment.development.ts | 5 ++- 9 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 src/app/core/store/home/home.actions.ts create mode 100644 src/app/core/store/home/home.model.ts create mode 100644 src/app/core/store/home/home.selectors.ts create mode 100644 src/app/core/store/home/home.state.ts create mode 100644 src/app/core/store/home/index.ts diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 10c3872ae..a477915a3 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -8,12 +8,17 @@ import Aura from '@primeng/themes/aura'; import { provideAnimations } from '@angular/platform-browser/animations'; import { provideHttpClient } from '@angular/common/http'; import { ConfirmationService } from 'primeng/api'; +import { AuthState } from '@core/store/auth'; +import { HomeState } from '@core/store/home'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), - provideStore([], withNgxsReduxDevtoolsPlugin({ disabled: false })), + provideStore( + [AuthState, HomeState], + withNgxsReduxDevtoolsPlugin({ disabled: false }), + ), providePrimeNG({ theme: { preset: Aura, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 87ec29d5d..27de9234f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,8 +1,9 @@ import { NgModule } from '@angular/core'; import { NgxsModule } from '@ngxs/store'; import { AuthState } from '@core/store/auth'; +import { HomeState } from '@core/store/home'; @NgModule({ - imports: [NgxsModule.forRoot([AuthState])], + imports: [NgxsModule.forRoot([AuthState, HomeState])], }) export class AppModule {} diff --git a/src/app/core/store/home/home.actions.ts b/src/app/core/store/home/home.actions.ts new file mode 100644 index 000000000..0b466a078 --- /dev/null +++ b/src/app/core/store/home/home.actions.ts @@ -0,0 +1,11 @@ +export class GetProjects { + static readonly type = '[Home] Get Projects'; +} + +export class GetNoteworthy { + static readonly type = '[Home] Get Noteworthy'; +} + +export class GetMostPopular { + static readonly type = '[Home] Get Most Popular'; +} diff --git a/src/app/core/store/home/home.model.ts b/src/app/core/store/home/home.model.ts new file mode 100644 index 000000000..2acbdd294 --- /dev/null +++ b/src/app/core/store/home/home.model.ts @@ -0,0 +1,7 @@ +import { Project } from '@osf/features/home/models/project.entity'; + +export interface HomeStateModel { + projects: Project[]; + noteworthy: Project[]; + mostPopular: Project[]; +} diff --git a/src/app/core/store/home/home.selectors.ts b/src/app/core/store/home/home.selectors.ts new file mode 100644 index 000000000..c333a4189 --- /dev/null +++ b/src/app/core/store/home/home.selectors.ts @@ -0,0 +1,21 @@ +import { Selector } from '@ngxs/store'; +import { HomeStateModel } from '@core/store/home/home.model'; +import { HomeState } from '@core/store/home/home.state'; +import { Project } from '@osf/features/home/models/project.entity'; + +export class HomeSelectors { + @Selector([HomeState]) + static getProjects(state: HomeStateModel): Project[] { + return state.projects; + } + + @Selector([HomeState]) + static getNoteworthy(state: HomeStateModel): Project[] { + return state.noteworthy; + } + + @Selector([HomeState]) + static getMostPopular(state: HomeStateModel): Project[] { + return state.mostPopular; + } +} diff --git a/src/app/core/store/home/home.state.ts b/src/app/core/store/home/home.state.ts new file mode 100644 index 000000000..e3d7809cd --- /dev/null +++ b/src/app/core/store/home/home.state.ts @@ -0,0 +1,50 @@ +import { HomeStateModel } from '@core/store/home/home.model'; +import { Action, State, StateContext } from '@ngxs/store'; +import { inject, Injectable } from '@angular/core'; +import { DashboardService } from '@osf/features/home/dashboard.service'; +import { + GetMostPopular, + GetNoteworthy, + GetProjects, +} from '@core/store/home/home.actions'; +import { tap } from 'rxjs'; + +@State({ + name: 'home', + defaults: { + projects: [], + noteworthy: [], + mostPopular: [], + }, +}) +@Injectable() +export class HomeState { + homeService = inject(DashboardService); + + @Action(GetProjects) + getProjects(ctx: StateContext) { + return this.homeService.getProjects().pipe( + tap((projects) => { + ctx.patchState({ projects: projects }); + }), + ); + } + + @Action(GetMostPopular) + getMostPopular(ctx: StateContext) { + return this.homeService.getMostPopular().pipe( + tap((mostPopular) => { + ctx.patchState({ mostPopular: mostPopular }); + }), + ); + } + + @Action(GetNoteworthy) + GetNoteworthy(ctx: StateContext) { + return this.homeService.getNoteworthy().pipe( + tap((noteworthy) => { + ctx.patchState({ noteworthy: noteworthy }); + }), + ); + } +} diff --git a/src/app/core/store/home/index.ts b/src/app/core/store/home/index.ts new file mode 100644 index 000000000..29b1d1de4 --- /dev/null +++ b/src/app/core/store/home/index.ts @@ -0,0 +1,4 @@ +export * from './home.actions'; +export * from './home.model'; +export * from './home.selectors'; +export * from './home.state'; diff --git a/src/app/features/home/home.component.ts b/src/app/features/home/home.component.ts index e63dcbb4c..69b6b1e01 100644 --- a/src/app/features/home/home.component.ts +++ b/src/app/features/home/home.component.ts @@ -9,6 +9,13 @@ import { SearchInputComponent } from '@shared/components/search-input/search-inp import { IS_MEDIUM, IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; import { DashboardService } from '@osf/features/home/dashboard.service'; +import { Store } from '@ngxs/store'; +import { + GetMostPopular, + GetNoteworthy, + GetProjects, + HomeSelectors, +} from '@core/store/home'; @Component({ selector: 'osf-home', @@ -28,9 +35,19 @@ export class HomeComponent implements OnInit { isMedium = toSignal(inject(IS_MEDIUM)); isMobile = toSignal(inject(IS_XSMALL)); dashboardService: DashboardService = inject(DashboardService); - projects = signal([]); - noteworthy = signal([]); - mostPopular = signal([]); + #store = inject(Store); + + protected readonly projects = this.#store.selectSignal( + HomeSelectors.getProjects, + ); + + protected readonly noteworthy = this.#store.selectSignal( + HomeSelectors.getNoteworthy, + ); + + protected readonly mostPopular = this.#store.selectSignal( + HomeSelectors.getMostPopular, + ); searchValue = signal(''); @@ -67,16 +84,8 @@ export class HomeComponent implements OnInit { } ngOnInit() { - this.dashboardService.getProjects().subscribe((res) => { - this.projects.set(res); - }); - - this.dashboardService.getNoteworthy().subscribe((res) => { - this.noteworthy.set(res); - }); - - this.dashboardService.getMostPopular().subscribe((res) => { - this.mostPopular.set(res); - }); + this.#store.dispatch(GetProjects); + this.#store.dispatch(GetNoteworthy); + this.#store.dispatch(GetMostPopular); } } diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index f274e5edf..2d4be37a9 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -1 +1,4 @@ -export const environment = {}; +export const environment = { + production: false, + apiUrl: 'https://api.staging4.osf.io/v2', +};