Skip to content

Commit

Permalink
refactor: get user info
Browse files Browse the repository at this point in the history
  • Loading branch information
cuixiaorui committed Jul 21, 2024
1 parent 34e943e commit efa5518
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 56 deletions.
41 changes: 28 additions & 13 deletions apps/api/src/user/tests/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe("UserService", () => {
});

describe("findUser", () => {
it("should return user info with membership details when user is a member", async () => {
it("should return user info with membership details", async () => {
const userId = "testUserId";
const logtoUserInfo = { id: userId, name: "Test User" };
const membershipDetails = {
Expand All @@ -46,6 +46,7 @@ describe("UserService", () => {
endDate: new Date(),
isActive: true,
};

(logtoServiceMock.logtoApi.get as jest.Mock).mockResolvedValue({ data: logtoUserInfo });
membershipServiceMock.isMember.mockResolvedValue(true);
membershipServiceMock.getMembershipDetails.mockResolvedValue(membershipDetails);
Expand All @@ -61,30 +62,44 @@ describe("UserService", () => {
});
});

it("should return user info without membership details when user is not a member", async () => {
it("should return undefined on error", async () => {
const userId = "testUserId";
const logtoUserInfo = { id: userId, name: "Test User" };

(logtoServiceMock.logtoApi.get as jest.Mock).mockResolvedValue({ data: logtoUserInfo });
membershipServiceMock.isMember.mockResolvedValue(false);
(logtoServiceMock.logtoApi.get as jest.Mock).mockRejectedValue(new Error("API Error"));

const result = await userService.findUser(userId);

expect(result).toBeUndefined();
});
});

describe("findCurrentUser", () => {
it("should return membership info for current user", async () => {
const userId = "testUserId";
const membershipDetails = {
type: "founder",
startDate: new Date(),
endDate: new Date(),
isActive: true,
};

membershipServiceMock.isMember.mockResolvedValue(true);
membershipServiceMock.getMembershipDetails.mockResolvedValue(membershipDetails);

const result = await userService.findCurrentUser(userId);

expect(result).toEqual({
...logtoUserInfo,
membership: {
isMember: false,
details: null,
isMember: true,
details: membershipDetails,
},
});
});

it("should return undefined when there's an error fetching user info", async () => {
it("should return undefined on error", async () => {
const userId = "testUserId";
membershipServiceMock.isMember.mockRejectedValue(new Error("Service Error"));

(logtoServiceMock.logtoApi.get as jest.Mock).mockRejectedValue(new Error("API Error"));

const result = await userService.findUser(userId);
const result = await userService.findCurrentUser(userId);

expect(result).toBeUndefined();
});
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class UserController {
@UseGuards(AuthGuard)
@Get()
async getCurrentUser(@User() user: UserEntity) {
const userInfo = await this.userService.findUser(user.userId);
const userInfo = await this.userService.findCurrentUser(user.userId);
return userInfo;
}

Expand Down
46 changes: 31 additions & 15 deletions apps/api/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,45 @@ export class UserService {

async findUser(uId: string) {
try {
const { data: logtoUserInfo } = await this.logtoService.logtoApi.get<LogtoUserInfo>(
`/api/users/${uId}`,
);
const isMember = await this.membershipService.isMember(uId);

let membershipInfo: MembershipDetails = null;
if (isMember) {
membershipInfo = await this.membershipService.getMembershipDetails(uId);
}

const { data: logtoUserInfo } = await this.logtoService.logtoApi.get(`/api/users/${uId}`);
const membershipInfo = await this.getMembershipInfo(uId);
return {
...logtoUserInfo,
membership: {
isMember,
details: membershipInfo,
},
membership: membershipInfo,
};
} catch (error) {
// 考虑是否需要更详细的错误处理
console.error("Error fetching user info:", error);
return undefined;
}
}

/**
* 返回当前登录用户的信息
* logto 相关的信息是在 client 获取得
* 所以这里只需要返回 earthworm 服务相关的信息就可以了(比如是否为会员)
* @param uId
* @returns
*/
async findCurrentUser(uId: string) {
try {
return {
membership: await this.getMembershipInfo(uId),
};
} catch (error) {
console.error("Error fetching current user info:", error);
return undefined;
}
}

private async getMembershipInfo(uId: string) {
const isMember = await this.membershipService.isMember(uId);
let details: MembershipDetails = null;
if (isMember) {
details = await this.membershipService.getMembershipDetails(uId);
}
return { isMember, details };
}

async updateUser(user: UserEntity, dto: UpdateUserDto) {
try {
const { data } = await this.logtoService.logtoApi.patch(`/api/users/${user.userId}`, dto);
Expand Down
14 changes: 12 additions & 2 deletions apps/client/api/user.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import type { SetupUserApiResponse, UserApiResponse } from "~/types";
import type { SetupUserApiResponse, User, UserApiResponse } from "~/types";
import { fetchUserInfo } from "~/services/auth";
import { http } from "./http";

export async function fetchSetupNewUser(data: { username: string; avatar: string }) {
return await http.post<SetupUserApiResponse, SetupUserApiResponse>("/user/setup", data);
}

export async function fetchCurrentUser() {
return await http.get<UserApiResponse, UserApiResponse>("/user");
// 这里必须在 client 获取 user info
// 他会触发 token 的刷新
const logtoUserInfo = await fetchUserInfo();
const extraInfo = await http.get<UserApiResponse, UserApiResponse>("/user");

return {
...logtoUserInfo,
...extraInfo,
avatar: logtoUserInfo!.picture || "", // 添加 avatar 字段,默认值为 picture ( picture 这个属性不够清晰 不喜欢)
} as User;
}
24 changes: 12 additions & 12 deletions apps/client/app.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<template>
<div
class="h-screen w-screen"
v-if="isSetupLoading"
>
<Loading />
</div>
<template v-else>
<NuxtLayout>
<HttpErrorProvider>
<HttpErrorProvider>
<div
class="h-screen w-screen"
v-if="isSetupLoading"
>
<Loading />
</div>
<template v-else>
<NuxtLayout>
<NuxtPage />
</HttpErrorProvider>
</NuxtLayout>
</template>
</NuxtLayout>
</template>
</HttpErrorProvider>
</template>

<script setup lang="ts">
Expand Down
4 changes: 4 additions & 0 deletions apps/client/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export async function getToken() {
return accessToken;
}

export function fetchUserInfo() {
return logto.fetchUserInfo();
}

export function getSignInCallback() {
let callback = sessionStorage.getItem("callback");
if (callback) {
Expand Down
12 changes: 0 additions & 12 deletions apps/client/types/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ export interface SetupUserApiResponse {
}

export interface UserApiResponse {
applicationId: string;
avatar: string;
createdAt: number;
customData: Record<string, any>;
id: string;
identities: Record<string, any>;
isSuspended: boolean;
lastSignInAt: number;
membership: {
details: {
endDate: string;
Expand All @@ -22,8 +14,4 @@ export interface UserApiResponse {
} | null;
isMember: boolean;
};
name: string | null;
primaryEmail: string;
primaryPhone: string | null;
username: string;
}
7 changes: 6 additions & 1 deletion apps/client/types/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { type UserInfoResponse } from "@logto/vue";

import { type UserApiResponse } from "../api/user";

export interface User extends UserApiResponse {}
export type User = UserInfoResponse &
UserApiResponse & {
avatar: string;
};

0 comments on commit efa5518

Please sign in to comment.