168 lines
4.6 KiB
JavaScript
168 lines
4.6 KiB
JavaScript
const bcrypt = require("bcryptjs")
|
||
const userDb = require("../db/user.db")
|
||
const notificationDb = require("../db/notification.db")
|
||
const refreshTokenDb = require("../db/refreshToken.db")
|
||
const { queueNotificationReadAll } = require("./redis/dbSync.service")
|
||
|
||
function assertPositiveInt(v, name = "id") {
|
||
const n = Number(v)
|
||
if (!Number.isInteger(n) || n <= 0) throw new Error(`Geçersiz ${name}.`)
|
||
return n
|
||
}
|
||
|
||
async function updateAvatarUrl(userId, url) {
|
||
const id = assertPositiveInt(userId, "userId")
|
||
if (!url || typeof url !== "string" || !url.trim())
|
||
throw new Error("Geçersiz URL.")
|
||
|
||
const select = { id: true, username: true, avatarUrl: true }
|
||
return userDb.updateUser({ id }, { avatarUrl: url.trim() }, { select })
|
||
}
|
||
|
||
async function getUserProfile(userId) {
|
||
const id = assertPositiveInt(userId, "userId")
|
||
const select = {
|
||
id: true,
|
||
username: true,
|
||
avatarUrl: true,
|
||
createdAt: true,
|
||
userBadges: {
|
||
orderBy: { earnedAt: "desc" },
|
||
select: {
|
||
earnedAt: true,
|
||
badge: { select: { id: true, name: true, iconUrl: true, description: true } },
|
||
},
|
||
},
|
||
notifications: {
|
||
orderBy: { createdAt: "desc" },
|
||
take: 3,
|
||
select: {
|
||
id: true,
|
||
message: true,
|
||
type: true,
|
||
createdAt: true,
|
||
readAt: true,
|
||
},
|
||
},
|
||
}
|
||
const user = await userDb.findUser({ id }, { select })
|
||
if (!user) return user
|
||
|
||
const formatDate = (value) => (value instanceof Date ? value.toISOString() : value ?? null)
|
||
|
||
const notifications = Array.isArray(user.notifications)
|
||
? user.notifications.map((n) => ({
|
||
...n,
|
||
createdAt: formatDate(n.createdAt),
|
||
readAt: formatDate(n.readAt),
|
||
unread: n.readAt == null,
|
||
}))
|
||
: []
|
||
|
||
const badges = Array.isArray(user.userBadges)
|
||
? user.userBadges.map((item) => ({
|
||
badge: item.badge
|
||
? {
|
||
id: item.badge.id,
|
||
name: item.badge.name,
|
||
iconUrl: item.badge.iconUrl ?? null,
|
||
description: item.badge.description ?? null,
|
||
}
|
||
: null,
|
||
earnedAt: formatDate(item.earnedAt),
|
||
}))
|
||
: []
|
||
|
||
return {
|
||
id: user.id,
|
||
username: user.username,
|
||
avatarUrl: user.avatarUrl ?? null,
|
||
createdAt: formatDate(user.createdAt),
|
||
notifications,
|
||
badges,
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
updateAvatarUrl,
|
||
getUserProfile,
|
||
markAllNotificationsRead,
|
||
getUserNotificationsPage,
|
||
changePassword,
|
||
}
|
||
|
||
async function markAllNotificationsRead(userId) {
|
||
const id = assertPositiveInt(userId, "userId")
|
||
const readAt = new Date().toISOString()
|
||
await queueNotificationReadAll({ userId: id, readAt })
|
||
return { queued: true, readAt }
|
||
}
|
||
|
||
async function getUserNotificationsPage(userId, page = 1, limit = 10) {
|
||
const id = assertPositiveInt(userId, "userId")
|
||
const pageNumber = assertPositiveInt(page, "page")
|
||
const take = assertPositiveInt(limit, "limit")
|
||
const skip = (pageNumber - 1) * take
|
||
|
||
const [total, notifications] = await Promise.all([
|
||
notificationDb.countNotifications({ userId: id }),
|
||
notificationDb.findNotifications(
|
||
{ userId: id },
|
||
{
|
||
orderBy: { createdAt: "desc" },
|
||
skip,
|
||
take,
|
||
select: {
|
||
id: true,
|
||
message: true,
|
||
type: true,
|
||
createdAt: true,
|
||
readAt: true,
|
||
},
|
||
}
|
||
),
|
||
])
|
||
|
||
const formatDate = (value) => (value instanceof Date ? value.toISOString() : value ?? null)
|
||
const results = Array.isArray(notifications)
|
||
? notifications.map((n) => ({
|
||
...n,
|
||
createdAt: formatDate(n.createdAt),
|
||
readAt: formatDate(n.readAt),
|
||
unread: n.readAt == null,
|
||
}))
|
||
: []
|
||
|
||
const totalPages = Math.ceil(total / take)
|
||
|
||
return {
|
||
page: pageNumber,
|
||
total,
|
||
totalPages,
|
||
results,
|
||
}
|
||
}
|
||
|
||
async function changePassword(userId, { currentPassword, newPassword }) {
|
||
const id = assertPositiveInt(userId, "userId")
|
||
if (!currentPassword || typeof currentPassword !== "string")
|
||
throw new Error("Mevcut şifre gerekli.")
|
||
if (!newPassword || typeof newPassword !== "string")
|
||
throw new Error("Yeni şifre gerekli.")
|
||
|
||
const user = await userDb.findUser(
|
||
{ id },
|
||
{ select: { id: true, passwordHash: true } }
|
||
)
|
||
if (!user) throw new Error("Kullanıcı bulunamadı.")
|
||
|
||
const isMatch = await bcrypt.compare(currentPassword, user.passwordHash)
|
||
if (!isMatch) throw new Error("Mevcut şifre hatalı.")
|
||
|
||
const passwordHash = await bcrypt.hash(newPassword, 10)
|
||
await userDb.updateUser({ id }, { passwordHash })
|
||
await refreshTokenDb.revokeAllUserRefreshTokens(id)
|
||
|
||
return { message: "Şifre güncellendi." }
|
||
}
|