Skip to content

Commit

Permalink
feat: [community/profiles] ajoute un badge sur la side bar (#308)
Browse files Browse the repository at this point in the history
* ajoute un badge sur le lien vers les profils ayant effectué une demande de relation
  • Loading branch information
matthieuaudemard committed May 5, 2023
1 parent 95d7942 commit 67036b1
Show file tree
Hide file tree
Showing 19 changed files with 128 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dynonuggets.refonteimplicaction.community.profile.controller;

import com.dynonuggets.refonteimplicaction.auth.service.AuthService;
import com.dynonuggets.refonteimplicaction.community.profile.dto.ProfileDto;
import com.dynonuggets.refonteimplicaction.community.profile.dto.ProfileUpdateRequest;
import com.dynonuggets.refonteimplicaction.community.profile.dto.enums.RelationCriteriaEnum;
Expand All @@ -25,6 +26,7 @@
public class ProfileController {

private final ProfileService profileService;
private final AuthService authService;

@GetMapping
@ResponseBody
Expand All @@ -33,7 +35,7 @@ public ResponseEntity<Page<ProfileDto>> getAllProfiles(
@RequestParam(defaultValue = "10") final int rows,
@RequestParam(value = "sortBy", defaultValue = "id") final String[] sortBy,
@RequestParam(value = "sortOrder", defaultValue = "ASC") final String sortOrder,
@RequestParam(value = "relationCriteria", defaultValue = "ANY") final RelationCriteriaEnum relationCriteria
@RequestParam(value = "relationCriteria", defaultValue = "ALL") final RelationCriteriaEnum relationCriteria
) {
final Pageable pageable = PageRequest.of(page, rows, Sort.by(Sort.Direction.valueOf(sortOrder), sortBy));
return ResponseEntity.ok(profileService.getAllProfiles(relationCriteria, pageable));
Expand All @@ -53,4 +55,9 @@ public ResponseEntity<ProfileDto> getProfileByUsername(@PathVariable final Strin
public ResponseEntity<ProfileDto> postProfileAvatar(@RequestParam final MultipartFile file, @PathVariable final String username) {
return ResponseEntity.ok(profileService.uploadAvatar(file, username));
}

@GetMapping("/friend-requests/count")
public Long getCountFriendRequestsByUsername() {
return profileService.countFriendRequestsByUsername(authService.getCurrentUser().getUsername());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public interface ProfileRepositoryCustom {

Page<ProfileModel> findAllProfilesWithRelationTypeCriteria(final String username, final RelationCriteriaEnum relationCriteria, final Pageable pageable);

Long countFriendRequestsByUsername(String username);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import com.dynonuggets.refonteimplicaction.community.profile.domain.repository.ProfileRepositoryCustom;
import com.dynonuggets.refonteimplicaction.community.profile.dto.enums.RelationCriteriaEnum;
import com.dynonuggets.refonteimplicaction.community.relation.domain.model.RelationModel;
import com.dynonuggets.refonteimplicaction.job.company.domain.model.CompanyModel;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
Expand All @@ -23,38 +23,58 @@ public class ProfileRepositoryCustomImpl implements ProfileRepositoryCustom {
private final EntityManager entityManager;

/**
* @param username nom de l'utilisateur courant
* @param username nom de lutilisateur courant
* @param relationCriteria type de relation.
* @param pageable options de pagination
* @return <ul>
* <li>{@link ANY} retourne tous profils qu'ils soient en relation ou non avec l'utilisateur courant à l'exception de l'utilisateur courant</li>
* <li>{@link ALL_FRIENDS} retourne toutes les relations confirmées de l'utilisateur (en tant que sender ou receiver) à l'exception de l'utilisateur courant</li>
* <li>{@link ONLY_FRIEND_REQUESTS} retourne seulement les profiles qui ont effectué une demande de relation avec l'utilisateur courant à l'exception de l'utilisateur courant</li>
* <li>{@link RelationCriteriaEnum#ALL} retourne tous profils qu'ils soient en relation ou non avec l'utilisateur courant à l'exception de l'utilisateur courant</li>
* <li>{@link RelationCriteriaEnum#ALL_FRIENDS} retourne toutes les relations confirmées de l'utilisateur (en tant que sender ou receiver) à l'exception de l'utilisateur courant</li>
* <li>{@link RelationCriteriaEnum#ONLY_FRIEND_REQUESTS} retourne seulement les profiles qui ont effectué une demande de relation avec l'utilisateur courant à l'exception de l'utilisateur courant</li>
*/
@Override
public Page<ProfileModel> findAllProfilesWithRelationTypeCriteria(final String username, final RelationCriteriaEnum relationCriteria, final Pageable pageable) {
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<ProfileModel> query = builder.createQuery(ProfileModel.class);
final Root<ProfileModel> p = query.from(ProfileModel.class);
final List<Predicate> queryPredicates = new ArrayList<>();
final Predicate[] queryPredicates = generateFilterPredicates(username, relationCriteria, query);

query.where(queryPredicates);

final CriteriaQuery<Long> countQuery = builder.createQuery(Long.class).where(queryPredicates);
countQuery.select(builder.count(countQuery.from(ProfileModel.class)));
final List<ProfileModel> profileModels = entityManager.createQuery(query)
.setFirstResult(pageable.getPageNumber() * pageable.getPageSize())
.getResultList();

return PageableExecutionUtils.getPage(profileModels, pageable, () -> entityManager.createQuery(countQuery).getSingleResult());
}

@Override
@Transactional(readOnly = true)
public Long countFriendRequestsByUsername(final String username) {
return findAllProfilesWithRelationTypeCriteria(username, ONLY_FRIEND_REQUESTS, Pageable.ofSize(Integer.MAX_VALUE)).getTotalElements();
}

queryPredicates.add(builder.isTrue(p.get("user").get("enabled")));
queryPredicates.add(builder.notEqual(p.get("user").get("username"), username));
private Predicate[] generateFilterPredicates(final String username, final RelationCriteriaEnum relationCriteria, final CriteriaQuery<?> query) {
final Root<ProfileModel> p = query.from(ProfileModel.class);
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final List<Predicate> queryPredicates = new ArrayList<>() {{
add(builder.isTrue(p.get("user").get("enabled")));
add(builder.notEqual(p.get("user").get("username"), username));
}};

if (List.of(ALL_FRIENDS, ONLY_FRIEND_REQUESTS).contains(relationCriteria)) {
final Subquery<Integer> subQuery = query.subquery(Integer.class);
final Root<RelationModel> r = subQuery.from(RelationModel.class);

// l'utilisateur doit être à la cible d'une invitation (receiver)
// l'utilisateur doit être à la cible dune invitation (receiver)
Predicate relationPredicate = builder.and(builder.equal(r.get("receiver").get("user").get("username"), username), builder.equal(r.get("sender"), p));

// si l'on veut remonter toutes les relatons de l'utilisateur, il faut aussi récupérer les profils pour lesquels il est à l'origine d'une relation
// si lon veut remonter toutes les relatons de lutilisateur, il faut aussi récupérer les profils pour lesquels il est à lorigine dune relation
if (relationCriteria == ALL_FRIENDS) {
final Predicate isSender = builder.and(builder.equal(r.get("sender").get("user").get("username"), username), builder.equal(r.get("receiver"), p));
relationPredicate = builder.or(relationPredicate, isSender);
}

// si 2 utilisateurs sont amis, alors la relation a été confirmée, sinon non
final Predicate confirmedPredicate = relationCriteria == ALL_FRIENDS
? builder.isNotNull(r.get("confirmedAt"))
: builder.isNull(r.get("confirmedAt"));
Expand All @@ -65,15 +85,7 @@ public Page<ProfileModel> findAllProfilesWithRelationTypeCriteria(final String u

queryPredicates.add(builder.exists(subQuery));
}

query.where(queryPredicates.toArray(Predicate[]::new));

final CriteriaQuery<Long> countQuery = builder.createQuery(Long.class).where(queryPredicates.toArray(Predicate[]::new));
countQuery.select(builder.count(countQuery.from(CompanyModel.class)));
final List<ProfileModel> profileModels = entityManager.createQuery(query)
.setFirstResult(pageable.getPageNumber() * pageable.getPageSize())
.getResultList();

return PageableExecutionUtils.getPage(profileModels, pageable, () -> entityManager.createQuery(countQuery).getSingleResult());
return queryPredicates.toArray(Predicate[]::new);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.dynonuggets.refonteimplicaction.community.profile.dto.enums;

public enum RelationCriteriaEnum {
ANY, ALL_FRIENDS, ONLY_FRIEND_REQUESTS
ALL,
ALL_FRIENDS,
ONLY_FRIEND_REQUESTS
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ public Page<ProfileDto> getAllProfiles(final RelationCriteriaEnum relationCriter
});
}

public Long countFriendRequestsByUsername(final String username) {
return profileRepository.countFriendRequestsByUsername(username);
}

/**
* Renvoie le profil s’il existe, sinon, si l’utilisateur existe, le profil est créé
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.dynonuggets.refonteimplicaction.community.profile.controller;

import com.dynonuggets.refonteimplicaction.auth.service.AuthService;
import com.dynonuggets.refonteimplicaction.community.profile.dto.ProfileDto;
import com.dynonuggets.refonteimplicaction.community.profile.dto.ProfileUpdateRequest;
import com.dynonuggets.refonteimplicaction.community.profile.dto.enums.RelationCriteriaEnum;
Expand Down Expand Up @@ -51,6 +52,9 @@ class ProfileControllerTest extends ControllerIntegrationTestBase {
@Getter
protected String baseUri = PROFILES_BASE_URI;

@MockBean
AuthService authService;

@MockBean
ProfileService profileService;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,19 +263,19 @@ void should_get_all_profiles_when_getAllProfiles() {
final UserModel currentUser = generateRandomUserModel();
final Pageable pageable = Pageable.ofSize(10);
final List<String> usernames = profileModels.stream().map(p -> p.getUser().getUsername()).collect(Collectors.toList());

given(authService.getCurrentUser()).willReturn(currentUser);
given(profileRepository.findAllProfilesWithRelationTypeCriteria(currentUser.getUsername(), RelationCriteriaEnum.ANY, pageable)).willReturn(profiles);
given(profileRepository.findAllProfilesWithRelationTypeCriteria(currentUser.getUsername(), RelationCriteriaEnum.ALL, pageable)).willReturn(profiles);
given(relationRepository.findAllRelationByUsernameWhereUserListAreSenderOrReceiver(currentUser.getUsername(), usernames)).willReturn(of());
profiles.forEach(p -> given(profileMapper.toDtoLight(p)).willReturn(ProfileDto.builder().username(p.getUser().getUsername()).build()));

// when
final Page<ProfileDto> result = profileService.getAllProfiles(RelationCriteriaEnum.ANY, pageable);
final Page<ProfileDto> result = profileService.getAllProfiles(RelationCriteriaEnum.ALL, pageable);

// then
final int size = profiles.getSize();
assertThat(result).hasSize(size);
verify(profileRepository, times(1)).findAllProfilesWithRelationTypeCriteria(currentUser.getUsername(), RelationCriteriaEnum.ANY, pageable);
verify(profileRepository, times(1)).findAllProfilesWithRelationTypeCriteria(currentUser.getUsername(), RelationCriteriaEnum.ALL, pageable);
verify(profileMapper, times(size)).toDtoLight(any());
}

Expand Down
2 changes: 1 addition & 1 deletion frontend-implicaction/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {UnauthorizedPageComponent} from './auth/pages/unauthorized-page/unauthor

const routes: Routes = [
{
path: 'auth',
path: Univers.AUTH.url,
loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule)
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,6 @@ import {ProfileListPageComponent} from "./pages/profile-list-page/profile-list-p
import {ProfileDetailsPageComponent} from "./pages/profile-details-page/profile-details-page.component";

const routes: Routes = [
{
path: 'relations/received',
component: ProfileListPageComponent,
},
{
path: 'relations/sent',
component: ProfileListPageComponent,
},
{
path: 'relations',
component: ProfileListPageComponent,
},
{
path: 'profiles/:username',
component: ProfileDetailsPageComponent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {EnumCodeLabelAbstract} from "../../../shared/enums/enum-code-label-abstract.enum";
import {Univers} from "../../../shared/enums/univers";

export enum ProfileMenuCode {
ALL = 'ALL',
ALL_FRIENDS = 'ALL_FRIENDS',
ONLY_FRIEND_REQUESTS = 'ONLY_FRIEND_REQUESTS'
}

export class ProfileMenuEnum extends EnumCodeLabelAbstract<ProfileMenuCode> {

static readonly ALL = new ProfileMenuEnum(ProfileMenuCode.ALL, 'Tous les utilisateurs', null, `/${Univers.COMMUNITY.url}/profiles`);
static readonly ALL_FRIENDS = new ProfileMenuEnum(ProfileMenuCode.ALL_FRIENDS, 'Mes amis', null, `/${Univers.COMMUNITY.url}/profiles`);
static readonly ONLY_FRIEND_REQUESTS = new ProfileMenuEnum(ProfileMenuCode.ONLY_FRIEND_REQUESTS, 'Invitations', null, `/${Univers.COMMUNITY.url}/profiles`);

constructor(readonly code: ProfileMenuCode, readonly label: string, readonly icon: string, readonly url: string) {
super(code, label);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import {Relation} from '../relation';
import {EnumCodeLabelAbstract} from '../../../shared/enums/enum-code-label-abstract.enum';

export enum RelationActionEnumCode {
CREATE = 'CREATE', DELETE = 'DELETE', CONFIRM = 'CONFIRM'
}

export interface RelationAction {
relation: Relation;
action: RelationActionEnumCode;
CREATE = 'CREATE',
DELETE = 'DELETE',
CONFIRM = 'CONFIRM'
}

export class RelationActionEnum extends EnumCodeLabelAbstract<RelationActionEnumCode> {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {Constants} from '../../../config/constants';
import {RelationActionEnumCode} from '../../models/enums/relation-action';
import {Relation} from '../../models/relation';
import {Subject} from 'rxjs';
import {RelationCriteriaEnum} from '../../models/enums/relation-criteria-enum';
import {RelationButton} from '../../models/relation-button';
import {ProfileMenuCode, ProfileMenuEnum} from "../../models/enums/profile-menu-enum";

@Component({
templateUrl: './profile-list-page.component.html',
Expand All @@ -30,28 +30,18 @@ export class ProfileListPageComponent implements OnInit, OnDestroy {
currentUser: User;
action: string;
loading = true;
relationTypeCriteria: RelationCriteriaEnum;
menuItems: MenuItem[] = [
{
label: 'Tous les utilisateurs',
routerLink: `/${Univers.COMMUNITY.url}/profiles`,
routerLinkActiveOptions: {exact: true}
},
{
label: 'Mes amis',
routerLink: `/${Univers.COMMUNITY.url}/profiles`,
queryParams: {filter: RelationCriteriaEnum.ALL_FRIENDS},
routerLinkActiveOptions: {exact: true}
},
{
label: 'Invitations',
routerLink: `/${Univers.COMMUNITY.url}/profiles`,
queryParams: {filter: RelationCriteriaEnum.ONLY_FRIEND_REQUESTS},
routerLinkActiveOptions: {exact: true}
}
];
selectedItemMenuCode: ProfileMenuCode;
menuItems: MenuItem[] = ProfileMenuEnum.values()
.map((item: ProfileMenuEnum) => ({
id: item.code,
label: item.label,
routerLink: item.url,
queryParams: {filter: item.code},
routerLinkActiveOptions: {queryParams: 'subset'}
} as MenuItem));

private onDestroySubject = new Subject<void>();
private onlyFriendRequestMenuItem: MenuItem;

constructor(
private authService: AuthService,
Expand All @@ -65,13 +55,20 @@ export class ProfileListPageComponent implements OnInit, OnDestroy {

ngOnInit(): void {
this.currentUser = this.authService.getPrincipal();
this.onlyFriendRequestMenuItem = this.menuItems.find(item => item.id === ProfileMenuCode.ONLY_FRIEND_REQUESTS);
this.route.queryParams
.pipe(takeUntil(this.onDestroySubject))
.subscribe(params => {
this.pageable.number = (params.page ?? 1) - 1;
this.relationTypeCriteria = params.filter ?? RelationCriteriaEnum.ANY;
this.selectedItemMenuCode = params.filter ?? ProfileMenuCode.ALL;
this.fetchProfiles();
});

this.profileService.getProfileRequestAsFriendCount().subscribe(relationCount => {
if (this.onlyFriendRequestMenuItem) {
this.onlyFriendRequestMenuItem.badge = relationCount ? `${relationCount}` : null;
}
});
}

trackByUsername = (index: number, item: Profile) => item.username;
Expand Down Expand Up @@ -130,11 +127,15 @@ export class ProfileListPageComponent implements OnInit, OnDestroy {
const index = this.pageable.content.findIndex(p => p.username === profile.username);
this.pageable.content.splice(index, 1, {...profile, relationWithCurrentUser});
this.profiles = [...this.pageable.content];
if (relationWithCurrentUser.confirmedAt) {
const relationCount = +this.onlyFriendRequestMenuItem.badge - 1;
this.onlyFriendRequestMenuItem.badge = relationCount ? `${relationCount}` : null;
}
}

private fetchProfiles(): void {
this.loading = true;
this.profileService.getAllProfiles(this.relationTypeCriteria ?? RelationCriteriaEnum.ANY, this.pageable)
this.profileService.getAllProfiles(this.selectedItemMenuCode ?? ProfileMenuCode.ALL, this.pageable)
.pipe(
finalize(() => this.loading = false),
take(1),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {HttpClient, HttpEvent, HttpRequest} from '@angular/common/http';
import {ApiEndpointsService} from '../../../core/services/api-endpoints.service';
import {Profile} from '../../models/profile';
import {Pageable} from '../../../shared/models/pageable';
import {RelationCriteriaEnum} from '../../models/enums/relation-criteria-enum';
import {ProfileMenuCode} from "../../models/enums/profile-menu-enum";

@Injectable({
providedIn: 'root'
Expand Down Expand Up @@ -37,7 +37,11 @@ export class ProfileService {
return this.http.request<Profile>(req);
}

getAllProfiles(relationCriteria: RelationCriteriaEnum, pageable: Pageable<Profile>): Observable<Pageable<Profile>> {
getAllProfiles(relationCriteria: ProfileMenuCode, pageable: Pageable<Profile>): Observable<Pageable<Profile>> {
return this.http.get<Pageable<Profile>>(this.apiEndpointsService.getAllProfilesEndpoint(relationCriteria, pageable));
}

getProfileRequestAsFriendCount(): Observable<number> {
return this.http.get<number>(this.apiEndpointsService.getProfileRequestAsFriendCountEndpoint(), {});
}
}
Loading

0 comments on commit 67036b1

Please sign in to comment.