Skip to content
Merged
4 changes: 2 additions & 2 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ model UserAgreement {
termsAgreed Boolean @map("terms_agreed")
privacyAgreed Boolean @map("privacy_agreed")
ageOver14Agreed Boolean @map("age_over_14_agreed")
marketingPushAgreed Boolean @map("marketing_push_agreed")
marketingEmailAgreed Boolean @map("marketing_email_agreed")
marketingPushAgreed Boolean @default(false) @map("marketing_push_agreed")
marketingEmailAgreed Boolean @default(false) @map("marketing_email_agreed")
agreedAt DateTime @default(now()) @map("agreed_at")

user User @relation(fields: [userId], references: [id], onDelete: Cascade)
Expand Down
33 changes: 33 additions & 0 deletions scripts/send-notice-push.js
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scripts 파일이 뭔가요? 푸시알림 전송하는거 jobs파일에서 처리합니다. 비동기 메시지 큐로 처리하니 참고해서 수정해주세요

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import "dotenv/config";
import { sendNoticePushNotification } from "../src/services/notice.service.js";

/**
* 공지사항 푸시 알림 전송 스크립트
*
* 사용 방법:
* node scripts/send-notice-push.js
*
* 기능:
* - 최근 10분 이내 생성된 공지사항을 찾습니다
* - marketingEnabled: true인 사용자에게 푸시 알림을 전송합니다
*/
const main = async () => {
try {
console.log("공지사항 푸시 알림 전송을 시작합니다...");

const result = await sendNoticePushNotification();

console.log("\n=== 전송 결과 ===");
console.log(`공지사항: ${result.notices.length}개`);
console.log(`대상 사용자: ${result.users}명`);
console.log(`성공: ${result.sent}건`);
console.log(`실패: ${result.failed || 0}건`);

process.exit(0);
} catch (error) {
console.error("에러 발생:", error);
process.exit(1);
}
};

main();
4 changes: 2 additions & 2 deletions src/controllers/mailbox.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export const handleGetAnonymousThreadLetters = async (req, res, next) => {
const userId = getAuthUserId(req);
if (!userId) throw new MailboxUnauthorizedError();

const { threadId } = req.params;
const result = await getAnonymousThreadLetters(userId, threadId);
const { sessionId } = req.params;
const result = await getAnonymousThreadLetters(userId, sessionId);

return res.status(200).success(result);
} catch (err) {
Expand Down
4 changes: 2 additions & 2 deletions src/errors/mailbox.error.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export class MailboxUnauthorizedError extends unauthorizedError {
}
}

export class MailboxInvalidThreadIdError extends BadRequestError {
constructor(code = "MAILBOX_INVALID_THREAD_ID", message = "threadId가 올바르지 않습니다.", data = null) {
export class MailboxInvalidSessionIdError extends BadRequestError {
constructor(code = "MAILBOX_INVALID_SESSION_ID", message = "sessionId가 올바르지 않습니다.", data = null) {
super(code, message, data);
}
}
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { isRestricted } from "./middlewares/restriction.middleware.js";
import { letterByAiKeywordSchema, letterToMeSchema, letterToOtherSchema, publicCarouselSchema } from "./schemas/letter.schema.js";
import { idParamSchema, ISOTimeSchema } from "./schemas/common.schema.js";
import { pushSubscriptionSchema, onboardingStep1Schema, updateInterestsSchema, updateProfileSchema, updateNotificationSettingsSchema, updateConsentsSchema, updateActivitySchema, createUserAgreementsSchema } from "./schemas/user.schema.js";
import { threadIdParamSchema } from "./schemas/mailbox.schema.js";
import { sessionIdParamSchema } from "./schemas/mailbox.schema.js";
import { noticeIdParamSchema } from "./schemas/notice.schema.js";
import { HandleGetHomeDashboard } from "./controllers/dashboard.controller.js";
import { handleInsertUserReport, handleGetUserReports, handleGetUserReport } from "./controllers/report.controller.js";
Expand Down Expand Up @@ -210,7 +210,7 @@ app.put("/users/me/push-subscriptions", isLogin, validate(pushSubscriptionSchema

// / 편지함
app.get("/mailbox/anonymous", isLogin, handleGetAnonymousThreads);
app.get("/mailbox/anonymous/threads/:threadId/letters", isLogin, validate(threadIdParamSchema), handleGetAnonymousThreadLetters);
app.get("/mailbox/anonymous/threads/:sessionId/letters", isLogin, validate(sessionIdParamSchema), handleGetAnonymousThreadLetters);
app.get("/mailbox/friends/threads/:friendId/letters", isLogin, validate(idParamSchema("friendId")), handleGetLetterFromFriend); // 친구 대화 목록 화면 조회
app.get("/mailbox/self", isLogin, handleGetSelfMailbox);

Expand Down
18 changes: 18 additions & 0 deletions src/repositories/letter.repository.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { prisma } from "../configs/db.config.js"
import { ReferenceNotFoundError } from "../errors/base.error.js";
import { LETTER_TYPE_ANON } from "../utils/user.util.js";

export const getLetterByUserIdAndAiKeyword = async (senderUserId, keyword) => {
const letter = await prisma.letter.findMany({
Expand Down Expand Up @@ -349,6 +350,23 @@ export const selectLetterByUserIds = async (userId, targetUserId) => {
return count;
};

export const selectAnonymousLetterCountByUserIds = async (userId, targetUserId) => {
if (!userId || !targetUserId) {
return 0;
}

const count = await prisma.letter.count({
where: {
letterType: LETTER_TYPE_ANON,
OR: [
{ senderUserId: userId, receiverUserId: targetUserId },
{ senderUserId: targetUserId, receiverUserId: userId },
],
},
});
return count;
};

export const selectRecentLetterByUserIds = async (userId, targetUserId) => {
return await prisma.letter.findFirst({
where: {
Expand Down
56 changes: 56 additions & 0 deletions src/repositories/user.repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,21 @@ export const getPushSubscription = async (userId) => {
return subscriptions;
}

export const getPushSubscriptionForMarketing = async (userId) => {
const subscriptions = await prisma.pushSubscription.findMany({
where: {
userId: userId,
user: {
notificationSetting: {
marketingEnabled: true
}
}
}
})

return subscriptions;
}

export const deletePushSubscription = async (id) => {
await prisma.pushSubscription.delete({
where: {id: id}
Expand Down Expand Up @@ -485,6 +500,7 @@ export const findReceivedLettersBySender = async ({ userId, senderUserId, letter
title: true,
content: true,
deliveredAt: true,
readAt: true,
createdAt: true,
question: {
select: {
Expand Down Expand Up @@ -528,6 +544,7 @@ export const findSentLettersByReceiver = async ({ userId, receiverUserId, letter
title: true,
content: true,
deliveredAt: true,
readAt: true,
createdAt: true,
question: {
select: {
Expand Down Expand Up @@ -639,6 +656,45 @@ export const findNoticeById = async (id) => {
});
};

export const findRecentNotices = async (minutes = 10) => {
const now = new Date();
const minutesAgo = new Date(now.getTime() - minutes * 60 * 1000);

return prisma.notice.findMany({
where: {
createdAt: {
gte: minutesAgo,
lte: now,
},
},
orderBy: { createdAt: "desc" },
select: {
id: true,
title: true,
createdAt: true,
},
});
};

export const findUsersWithMarketingPushEnabled = async () => {
const users = await prisma.user.findMany({
where: {
isDeleted: false,
notificationSetting: {
marketingEnabled: true,
},
pushSubscriptions: {
some: {},
},
},
select: {
id: true,
},
});

return users.map((user) => user.id);
};

// ========== Notification Repository ==========
/**
* 알림 설정 조회
Expand Down
4 changes: 2 additions & 2 deletions src/schemas/mailbox.schema.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { z } from "zod";

// ========== Mailbox Schema ==========
export const threadIdParamSchema = z.object({
export const sessionIdParamSchema = z.object({
params: z.object({
threadId: z.coerce.number("threadId는 숫자여야 합니다.").int("threadId는 정수여야 합니다.").positive("threadId는 1부터 유효합니다.")
sessionId: z.coerce.number("sessionId는 숫자여야 합니다.").int("sessionId는 정수여야 합니다.").positive("sessionId는 1부터 유효합니다.")
})
});
Loading