Skip to content

Commit

Permalink
[feat]: get user avatar to have it in user state for components
Browse files Browse the repository at this point in the history
  • Loading branch information
llucasspot committed Jan 28, 2024
1 parent 1fc45e6 commit 60bf754
Show file tree
Hide file tree
Showing 18 changed files with 195 additions and 181 deletions.
3 changes: 2 additions & 1 deletion expo-boomboomapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"format-check": "prettier --check 'src/**/*.{js,jsx,ts,tsx,json,css,md}'",
"lint": "eslint . --ext=.ts",
"format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,json,css,md}'",
"swagger:generate": "openapi-generator-cli generate -i https://raw.githubusercontent.com/llucasspot/boomboom-back/main/docs/swagger.json -g typescript-axios -o ./swagger --additional-properties=npmName=restClient,supportsES6=true,withInterfaces=true"
"swagger:generate": "openapi-generator-cli generate -i https://raw.githubusercontent.com/llucasspot/boomboom-back/main/docs/swagger.json -g typescript-axios -o ./swagger --additional-properties=npmName=restClient,supportsES6=true,withInterfaces=true",
"swagger:generate:local": "openapi-generator-cli generate -i /Users/test/Public/boomboom-back/docs/swagger.json -g typescript-axios -o ./swagger --additional-properties=npmName=restClient,supportsES6=true,withInterfaces=true"
},
"jest": {
"preset": "jest-expo"
Expand Down
46 changes: 34 additions & 12 deletions expo-boomboomapp/src/api/ProfileApiService/ProfileApiMockService.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
import { singleton } from "tsyringe";
import { AuthApiInterface } from "@swagger/api";
import { Configuration } from "@swagger/configuration";
import { buildApiRequester } from "@utils/api.utils";
import { inject, singleton } from "tsyringe";

import {
CreateProfileBody,
EditProfileBody,
ProfileApiServiceI,
} from "./ProfileApiServiceI";
import { EditProfileBody, ProfileApiServiceI } from "./ProfileApiServiceI";
import { user_yohan } from "../../mocks/mokes";
import ConfigurationService from "../../services/ConfigurationService/ConfigurationService";
import StorageService from "../../services/StorageService/StorageService";
import ServiceInterface from "../../tsyringe/ServiceInterface";
import { buildAxiosMockResponse } from "../utils";

@singleton()
export class ProfileApiMockService implements ProfileApiServiceI {
async createProfile(createProfileBody: CreateProfileBody) {
return Promise.resolve(user_yohan);
export class ProfileApiMockService
extends ProfileApiServiceI
implements AuthApiInterface
{
constructor(
@inject(ServiceInterface.ConfigurationService)
protected configurationService: ConfigurationService,
@inject(ServiceInterface.StorageServiceI)
protected storageService: StorageService,
) {
const baseUrl = configurationService.getWiremockApiUrl();
super(new Configuration(), baseUrl, buildApiRequester(baseUrl), () =>
this.storageService.getAuthenticateToken(),
);
}

async getProfile() {
return Promise.resolve(user_yohan);
override createProfile() {
return Promise.resolve(buildAxiosMockResponse(user_yohan));
}

override getProfile() {
return Promise.resolve(buildAxiosMockResponse(user_yohan));
}

async editProfile(editedProfileBody: EditProfileBody) {
return Promise.resolve({ ...user_yohan, ...editedProfileBody });
}

async uploadAvatar(uri: string): Promise<void> {}
async uploadAvatarByUri(uri: string): Promise<void> {}

async getBlobedAvatar() {
return Promise.resolve(require("@assets/mokes/yohan.png"));
}
}
73 changes: 12 additions & 61 deletions expo-boomboomapp/src/api/ProfileApiService/ProfileApiService.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,31 @@
import * as FileSystem from "expo-file-system";
import { AuthApiInterface } from "@swagger/api";
import { Configuration } from "@swagger/configuration";
import { buildApiRequester } from "@utils/api.utils";
import { inject, singleton } from "tsyringe";

import {
CreateProfileBody,
EditProfileBody,
ProfileApiServiceI,
ProfileI,
} from "./ProfileApiServiceI";
import { EditProfileBody, ProfileApiServiceI } from "./ProfileApiServiceI";
import ConfigurationService from "../../services/ConfigurationService/ConfigurationService";
import ErrorService from "../../services/ErrorService/ErrorService";
import StorageService from "../../services/StorageService/StorageService";
import ServiceInterface from "../../tsyringe/ServiceInterface";
import { ApiService } from "../ApiService";

@singleton()
export class ProfileApiService
extends ApiService
implements ProfileApiServiceI
extends ProfileApiServiceI
implements AuthApiInterface
{
constructor(
@inject(ServiceInterface.StorageServiceI)
protected storageService: StorageService,
@inject(ServiceInterface.ConfigurationService)
protected configurationService: ConfigurationService,
@inject(ServiceInterface.ErrorService)
protected errorService: ErrorService,
@inject(ServiceInterface.StorageServiceI)
protected storageService: StorageService,
) {
super("auth/profile", storageService, configurationService, errorService);
}

async createProfile(createProfileBody: CreateProfileBody) {
const res = await this.apiRequester.post<ProfileI>("/", createProfileBody);
return res.data;
}

async getProfile() {
const res = await this.apiRequester.get<ProfileI>("/");
return res.data;
const baseUrl = configurationService.getApiUrl().replace("/api", "");
super(new Configuration(), baseUrl, buildApiRequester(baseUrl), () =>
this.storageService.getAuthenticateToken(),
);
}

async editProfile(editedProfileBody: EditProfileBody) {
// TODO url
const res = await this.apiRequester.put<ProfileI>(
"/TODO",
editedProfileBody,
);
return res.data;
}

async uploadAvatar(uri: string): Promise<void> {
try {
const response = await FileSystem.uploadAsync(
`${this.apiRequester.getUri()}/avatar`,
uri,
{
httpMethod: "POST",
fieldName: "avatar",
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
headers: {
Authorization: `Bearer ${await this.storageService.getAuthenticateToken()}`,
},
},
);
this.handleFileSystemUploadAsyncResponse(response);
} catch (error) {
// TODO handle error
console.error("Error uploading image:", error);
}
}

private handleFileSystemUploadAsyncResponse(
response: FileSystem.FileSystemUploadResult,
) {
if (response.status >= 200 && response.status < 300) {
return response;
}
throw response.body;
}
}
83 changes: 53 additions & 30 deletions expo-boomboomapp/src/api/ProfileApiService/ProfileApiServiceI.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
import { Gender } from "../../services/UserService/userServiceI";
import { AuthApi, AuthApiInterface, CreateProfileRequest } from "@swagger/api";
import { Configuration } from "@swagger/configuration";
import { AxiosInstance } from "axios/index";
import * as FileSystem from "expo-file-system";

export abstract class ProfileI {
abstract user_id: string;
abstract date_of_birth: string;
abstract description: string;
abstract avatar: string;
abstract prefered_gender_id: Gender;
abstract userId: string;
// TODO name & trackIds is not send my backend in getProfile endpoint
abstract name: string;
abstract trackIds: string[];
}

export type CreateProfileBody = {
dateOfBirth: string;
description: string;
preferedGenderId: Gender;
genderId: Gender;
trackIds: string[];
name: string;
};

export type EditProfileBody = Partial<CreateProfileBody>;
export type EditProfileBody = Partial<CreateProfileRequest>;

export abstract class ProfileApiServiceI {
abstract createProfile(
createProfileBody: CreateProfileBody,
): Promise<ProfileI>;
export abstract class ProfileApiServiceI
extends AuthApi
implements AuthApiInterface
{
constructor(
configuration: Configuration,
basePath: string,
axios: AxiosInstance,
private authTokenGetter: () => Promise<string | null>,
) {
super(configuration, basePath, axios);
}

abstract getProfile(): Promise<ProfileI>;
async uploadAvatarByUri(uri: string): Promise<void> {
try {
const response = await FileSystem.uploadAsync(
`${this.basePath}/api/auth/profile/avatar`,
uri,
{
httpMethod: "POST",
fieldName: "avatar",
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
headers: {
Authorization: `Bearer ${await this.authTokenGetter()}`,
},
},
);
this.handleFileSystemUploadAsyncResponse(response);
} catch (error) {
// TODO handle error
console.error("Error uploading image:", error);
}
}

abstract editProfile(editedProfileBody: EditProfileBody): Promise<ProfileI>;
private handleFileSystemUploadAsyncResponse(
response: FileSystem.FileSystemUploadResult,
) {
if (response.status >= 200 && response.status < 300) {
return response;
}
throw response.body;
}

abstract uploadAvatar(uri: string): Promise<void>;
async getBlobedAvatar() {
const response = await this.getAvatar({ responseType: "blob" });
const blob = new Blob([response.data]);
// localUrl is redundant,
// but funny thing, if we use a direct return the image will not load on the component
const localUrl = URL.createObjectURL(blob);
return localUrl;
}
}
19 changes: 6 additions & 13 deletions expo-boomboomapp/src/api/UserApiService/UserApiMockService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ import {
} from "@swagger/api";
import { Configuration } from "@swagger/configuration";
import { buildApiRequester } from "@utils/api.utils";
import {
AxiosResponse,
InternalAxiosRequestConfig,
RawAxiosRequestConfig,
} from "axios";
import { AxiosResponse, RawAxiosRequestConfig } from "axios";
import { inject, singleton } from "tsyringe";
import { v4 as uuidv4 } from "uuid";

import { user_helena, user_isabella, user_jessica } from "../../mocks/mokes";
import ConfigurationService from "../../services/ConfigurationService/ConfigurationService";
import ServiceInterface from "../../tsyringe/ServiceInterface";
import { buildAxiosMockResponse } from "../utils";

@singleton()
export class UserApiMockService extends UserApi implements UserApiInterface {
Expand All @@ -31,8 +28,8 @@ export class UserApiMockService extends UserApi implements UserApiInterface {
options?: RawAxiosRequestConfig,
): Promise<AxiosResponse<ApiUsersGet200Response>> {
const stackProfiles = [user_isabella, user_helena, user_jessica];
return Promise.resolve({
data: {
return Promise.resolve(
buildAxiosMockResponse({
data: stackProfiles.map(({ user, songs }) => {
return {
user: {
Expand All @@ -42,11 +39,7 @@ export class UserApiMockService extends UserApi implements UserApiInterface {
songs,
};
}),
},
status: 200,
statusText: "statusText",
headers: {},
config: {} as InternalAxiosRequestConfig<any>,
});
}),
);
}
}
11 changes: 11 additions & 0 deletions expo-boomboomapp/src/api/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { InternalAxiosRequestConfig } from "axios";

export function buildAxiosMockResponse<T>(data: T) {
return {
data,
status: 200,
statusText: "statusText",
headers: {},
config: {} as InternalAxiosRequestConfig<any>,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,14 @@ export default function FavoriteSongs({
await profileApiService.createProfile({
dateOfBirth: user.dateOfBirth,
description: user.description,
preferedGenderId: user.gender,
// TODO add genderId in form
genderId: user.gender,
preferedGenderId: user.preferedGenderId,
genderId: user.genderId,
trackIds: mySongs.map((song) => song.trackId),
name: user.fullName,
});
await profileApiService.uploadAvatar(user.profilePicture.uri as string);
await profileApiService.uploadAvatarByUri(
user.profilePicture.uri as string,
);
navigation.navigate(RegisterStackScreen.WELCOME_SCREEN);
} catch (err) {
// TODO handle error better
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,20 @@ export function ProfileForm({
// .date()
.string()
.required("Date of birth is required"),
gender: yup
genderId: yup
.mixed<Gender>()
.oneOf(Object.values(Gender) as Gender[], "Invalid gender")
.required("Gender is required"),
preferedGenderId: yup
.mixed<Gender>()
.oneOf(Object.values(Gender) as Gender[], "Invalid gender")
.required("Prefered gender is required"),
description: yup.string(),
}),
),
defaultValues: {
gender: Gender.NO_SPECIFIC,
genderId: Gender.NO_SPECIFIC,
preferedGenderId: Gender.NO_SPECIFIC,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@ import iconUser from "@assets/Registration/icon_user.png";
import { buildImageSource } from "@utils/images.utils";
import * as ImagePicker from "expo-image-picker";
import { useEffect } from "react";
import {
Image,
ImageSourcePropType,
ImageStyle,
TouchableOpacity,
View,
} from "react-native";
import { Image, ImageStyle, TouchableOpacity, View } from "react-native";

import useEStyles from "../../../hooks/useEStyles";
import { RegisterStackParamsList } from "../../../navigation/RegisterStackScreenNavigator/RegisterStack";
Expand Down Expand Up @@ -54,7 +48,7 @@ export default function UploadAvatar({
// TODO to see if we keep type & name in state
userService.updateUserState({
profilePicture: {
uri: image.uri as ImageSourcePropType,
uri: image.uri,
type: image.type ?? "image",
name: image.fileName ?? "",
},
Expand Down
Loading

0 comments on commit 60bf754

Please sign in to comment.