Skip to content

Commit 81e0ad9

Browse files
Allow getting a profile by id or username
1 parent 60ef264 commit 81e0ad9

3 files changed

Lines changed: 54 additions & 20 deletions

File tree

src/api/v1/profiles/profiles.router.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ profilesRouter.get('/followers', Validators.authValidator, async (req, res) => {
2424
res.json(await Service.getAllFollowers(userId, filters));
2525
});
2626

27-
profilesRouter.get('/:id', Validators.authValidator, async (req, res) => {
27+
profilesRouter.get('/:idOrUsername', Validators.authValidator, async (req, res) => {
2828
const userId = Utils.getCurrentUserIdFromReq(req)!;
29-
res.json(await Service.getProfileById(req.params.id, userId));
29+
res.json(await Service.getProfileByIdOrUsername(req.params.idOrUsername, userId));
3030
});
3131

3232
profilesRouter.patch('/', Validators.authValidator, async (req, res) => {

src/api/v1/profiles/profiles.service.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as Types from '@/types';
22
import * as Utils from '@/lib/utils';
33
import * as Schema from './profile.schema';
4-
import { AppNotFoundError, AppUniqueConstraintViolationError } from '@/lib/app-error';
4+
import * as AppError from '@/lib/app-error';
55
import { Prisma, Profile, User } from '@/../prisma/client';
66
import db from '@/lib/db';
77

@@ -76,7 +76,29 @@ export const getProfileById = async (profileId: Profile['id'], userId: User['id'
7676
}),
7777
);
7878
if (profile) return prepareProfileData(profile, userId);
79-
throw new AppNotFoundError('Profile not found');
79+
throw new AppError.AppNotFoundError('Profile not found');
80+
};
81+
82+
export const getProfileByUsername = async (username: User['username'], userId: User['id']) => {
83+
const profiles = await Utils.handleDBKnownErrors(
84+
db.profile.findMany({
85+
...getExtendedProfileAggregationArgs(userId),
86+
where: { user: { username } },
87+
}),
88+
);
89+
if (profiles.length === 1) return prepareProfileData(profiles[0], userId);
90+
throw new AppError.AppNotFoundError('Profile not found');
91+
};
92+
93+
export const getProfileByIdOrUsername = async (idOrUsername: string, userId: User['id']) => {
94+
try {
95+
return await getProfileByUsername(idOrUsername, userId);
96+
} catch (error) {
97+
if (error instanceof AppError.AppNotFoundError) {
98+
return await getProfileById(idOrUsername, userId);
99+
}
100+
throw error;
101+
}
80102
};
81103

82104
export const updateProfileByUserId = async (userId: User['id'], data: Schema.ValidProfile) => {
@@ -102,7 +124,7 @@ export const createFollowing = async (userId: User['id'], { profileId }: Schema.
102124
}),
103125
);
104126
} catch (error) {
105-
if (error instanceof AppUniqueConstraintViolationError) return;
127+
if (error instanceof AppError.AppUniqueConstraintViolationError) return;
106128
throw error;
107129
}
108130
};
@@ -112,7 +134,7 @@ export const deleteFollowing = async (userId: User['id'], { profileId }: Schema.
112134
await Utils.handleDBKnownErrors(
113135
db.$transaction(async (prismaClient) => {
114136
const currentProfile = await prismaClient.profile.findUnique({ where: { userId } });
115-
if (!currentProfile) throw new AppNotFoundError('Profile not found');
137+
if (!currentProfile) throw new AppError.AppNotFoundError('Profile not found');
116138
await prismaClient.follows.delete({
117139
where: { profileId_followerId: { followerId: currentProfile.id, profileId } },
118140
});

src/tests/api/v1/profiles.int.test.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -145,21 +145,33 @@ describe('Profile', async () => {
145145
});
146146
});
147147

148-
describe(`${PROFILES_URL}/:id`, () => {
149-
it('should respond with 401 on an unauthenticated request', async () => {
150-
const res = await api.get(`${PROFILES_URL}/${dbUserOne.profile!.id}`);
151-
assertUnauthorizedErrorRes(res);
152-
});
153-
154-
it('should respond with a profile, on an authenticated request', async () => {
155-
const { authorizedApi } = await prepForAuthorizedTest(userOneData);
156-
const res = await authorizedApi.get(`${PROFILES_URL}/${dbUserOne.profile!.id}`);
157-
const profile = res.body as Types.PublicProfile;
158-
expect(res.statusCode).toBe(200);
159-
expect(res.type).toMatch(/json/);
160-
assertPublicProfile(profile);
148+
const getProfileTestData = [
149+
{ key: 'id', value: dbUserOne.profile!.id },
150+
{ key: 'username', value: dbUserOne.username },
151+
];
152+
for (const { key, value } of getProfileTestData) {
153+
describe(`${PROFILES_URL}/:${key}`, () => {
154+
it('should respond with 401 on an unauthenticated request', async () => {
155+
const res = await api.get(`${PROFILES_URL}/${value}`);
156+
assertUnauthorizedErrorRes(res);
157+
});
158+
159+
it(`should respond with 400 on an invalid ${key}`, async () => {
160+
const { authorizedApi } = await prepForAuthorizedTest(userOneData);
161+
const res = await authorizedApi.get(`${PROFILES_URL}/invalid`);
162+
assertInvalidIdErrorRes(res);
163+
});
164+
165+
it('should respond with a profile', async () => {
166+
const { authorizedApi } = await prepForAuthorizedTest(userOneData);
167+
const res = await authorizedApi.get(`${PROFILES_URL}/${value}`);
168+
const profile = res.body as Types.PublicProfile;
169+
expect(res.statusCode).toBe(200);
170+
expect(res.type).toMatch(/json/);
171+
assertPublicProfile(profile);
172+
});
161173
});
162-
});
174+
}
163175

164176
describe(`${PROFILES_URL}/following`, () => {
165177
const sortedUsers = dbAllUsersSorted.filter((u) => u.id !== dbUserOne.id);

0 commit comments

Comments
 (0)