Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/api/v1/profiles/profiles.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Schema from './profile.schema';
import * as Service from './profiles.service';
import * as Validators from '@/middlewares/validators';
import { Router } from 'express';
import io from '@/lib/io';

export const profilesRouter = Router();

Expand All @@ -29,9 +30,24 @@ profilesRouter.get('/:idOrUsername', Validators.authValidator, async (req, res)
res.json(await Service.getProfileByIdOrUsername(req.params.idOrUsername, userId));
});

profilesRouter.get('/:id/online', Validators.authValidator, async (req, res) => {
// Every socket should be in 2 rooms: socket-id (default), and profile-id (joined on connection)
const online = (await io.fetchSockets()).some((socket) => socket.rooms.has(req.params.id));
res.json(online);
});

profilesRouter.patch('/', Validators.authValidator, async (req, res) => {
const userId = Utils.getCurrentUserIdFromReq(req)!;
res.json(await Service.updateProfileByUserId(userId, Schema.profileSchema.parse(req.body)));
const updates = Schema.profileSchema.parse(req.body);
const updatedProfile = await Service.updateProfileByUserId(userId, updates);
res.json(updatedProfile).on('finish', () => {
const { tangible, visible } = updates;
const { id: profileId } = updatedProfile;
if (tangible !== undefined) io.volatile.except(profileId).emit('chats:updated');
if (visible !== undefined) {
io.volatile.except(profileId).emit(`${visible ? 'online' : 'offline'}:${profileId}`);
}
});
});

profilesRouter.post('/following/:profileId', Validators.authValidator, async (req, res) => {
Expand Down
20 changes: 10 additions & 10 deletions src/api/v1/profiles/profiles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ type ProfilePayload = Prisma.ProfileGetPayload<{
};
}>;

export const prepareProfileData = (
export const prepareProfiles = (
data: ProfilePayload | ProfilePayload[],
userId: User['id'],
): Types.PublicProfile | Types.PublicProfile[] => {
): Types.PublicProfile[] => {
const arrayGiven = Array.isArray(data);
const profiles = arrayGiven ? data : [data];
const preparedProfiles = profiles.map(({ followers, ...profile }) => {
Expand All @@ -24,7 +24,7 @@ export const prepareProfileData = (
followedByCurrentUser: followers.some((f) => f.follower.userId === userId),
};
});
return arrayGiven ? preparedProfiles : preparedProfiles[0];
return preparedProfiles;
};

const getProfilePaginationArgs = (filters: Types.ProfileFilters, limit = 10) => {
Expand Down Expand Up @@ -56,7 +56,7 @@ const getNameFilterArgs = (nameFilter: Types.ProfileFilters['name']) => {
};

export const getAllProfiles = async (userId: User['id'], filters: Types.ProfileFilters = {}) => {
return prepareProfileData(
return prepareProfiles(
await Utils.handleDBKnownErrors(
db.profile.findMany({
...getProfilePaginationArgs(filters),
Expand All @@ -75,7 +75,7 @@ export const getProfileById = async (profileId: Profile['id'], userId: User['id'
where: { id: profileId },
}),
);
if (profile) return prepareProfileData(profile, userId);
if (profile) return prepareProfiles(profile, userId)[0];
throw new AppError.AppNotFoundError('Profile not found');
};

Expand All @@ -86,7 +86,7 @@ export const getProfileByUsername = async (username: User['username'], userId: U
where: { user: { username } },
}),
);
if (profiles.length === 1) return prepareProfileData(profiles[0], userId);
if (profiles.length === 1) return prepareProfiles(profiles[0], userId)[0];
throw new AppError.AppNotFoundError('Profile not found');
};

Expand All @@ -102,7 +102,7 @@ export const getProfileByIdOrUsername = async (idOrUsername: string, userId: Use
};

export const updateProfileByUserId = async (userId: User['id'], data: Schema.ValidProfile) => {
return prepareProfileData(
return prepareProfiles(
await Utils.handleDBKnownErrors(
db.profile.update({
...getExtendedProfileAggregationArgs(userId),
Expand All @@ -111,7 +111,7 @@ export const updateProfileByUserId = async (userId: User['id'], data: Schema.Val
}),
),
userId,
);
)[0];
};

export const createFollowing = async (userId: User['id'], { profileId }: Schema.ValidFollowing) => {
Expand Down Expand Up @@ -147,7 +147,7 @@ export const deleteFollowing = async (userId: User['id'], { profileId }: Schema.
};

export const getAllFollowing = async (userId: User['id'], filters: Types.ProfileFilters = {}) => {
return prepareProfileData(
return prepareProfiles(
await Utils.handleDBKnownErrors(
db.profile.findMany({
...getProfilePaginationArgs(filters),
Expand All @@ -163,7 +163,7 @@ export const getAllFollowing = async (userId: User['id'], filters: Types.Profile
};

export const getAllFollowers = async (userId: User['id'], filters: Types.ProfileFilters = {}) => {
return prepareProfileData(
return prepareProfiles(
await Utils.handleDBKnownErrors(
db.profile.findMany({
...getProfilePaginationArgs(filters),
Expand Down
31 changes: 17 additions & 14 deletions src/lib/io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,31 @@ const io = new Server({ cors: corsOptions, serveClient: false });

io.on('connection', (socket) => {
const { user } = socket.handshake.auth as Partial<AuthResponse>;
const { username, profile } = user ?? { username: 'Anonymous', profile: null };

if (profile) {
const { id } = profile;

socket.broadcast.emit(`online:${id}`);
socket.on('disconnecting', () => socket.broadcast.emit(`offline:${id}`));

socket
.join(id)
?.catch((error: unknown) => logger.error(`Failed to join ${username} in ${id} room`, error));

if (user) {
void socket.join(user.id)?.catch();
if (user.profile) {
socket
.join(user.profile.id)
?.catch((error: unknown) => logger.error('Failed to join a socket room', error));
}
db.profile
.update({ where: { userId: user.id }, data: { lastSeen: new Date() } })
.then(() => logger.info(`Marked ${user.username} as seen`))
.catch((error: unknown) => logger.error(`Failed to mark ${user.username} as seen`, error));
.update({ where: { id }, data: { lastSeen: new Date() } })
.then(() => logger.info(`Marked ${username} as seen`))
.catch((error: unknown) => logger.error(`Failed to mark ${username} as seen`, error));
}

const username = user?.username;
const { clientsCount } = io.engine;

logger.info('A socket client is connected', { username, clientsCount });

socket.on('disconnect', () =>
logger.info('A socket client is disconnected.', { username, clientsCount }),
);
socket.on('disconnect', () => {
logger.info('A socket client is disconnected.', { username, clientsCount });
});
});

export { io };
Expand Down