From cadd69086bbcdb28895f24e38fd9037510ab5acc Mon Sep 17 00:00:00 2001 From: ptyoiy <56474564+ptyoiy@users.noreply.github.com> Date: Mon, 6 May 2024 05:17:25 +0900 Subject: [PATCH] fix: notice read controller (logic, caching) --- controllers/common_method/user_information.ts | 19 ++--- controllers/common_method/validator.ts | 8 +- controllers/event/event_bookmark.ts | 4 +- controllers/event/event_like.ts | 4 +- controllers/event/request/request.ts | 4 +- controllers/jwt/jwt.ts | 2 + controllers/notice/index.ts | 5 +- controllers/notice/notice_like.ts | 4 +- controllers/notice/notice_read.ts | 77 +++++-------------- controllers/notice/request/request.ts | 4 +- models/reads.ts | 3 +- models/reads_assets.ts | 7 +- models/types.d.ts | 7 +- routes/notice.ts | 2 +- 14 files changed, 61 insertions(+), 89 deletions(-) diff --git a/controllers/common_method/user_information.ts b/controllers/common_method/user_information.ts index beedc21..bf55f2f 100644 --- a/controllers/common_method/user_information.ts +++ b/controllers/common_method/user_information.ts @@ -121,20 +121,21 @@ export async function getNoticeLikeInfo(userId: string) { export async function getNoticeReadInfo(userId: string) { const redisKey = `user:noticeReads:${userId}`; - const noticeReadsRedis = await redisClient.get(redisKey); + const noticeReadsRedis = await redisClient.sMembers(redisKey); if (noticeReadsRedis) { - return JSON.parse(noticeReadsRedis); + return noticeReadsRedis; } - const noticeReads = await Read.findAll({ + const noticeReads = await Read.findOne({ where: { fk_user_id: userId }, include: [{ model: ReadAsset, as: 'ReadAssets' }] }); - - if (noticeReads) { - await redisClient.set(redisKey, JSON.stringify(noticeReads), { EX: EXPIRE }); - return noticeReads; - } else { - return []; + if (!noticeReads) { + // 없을 경우 잘못된 body + throw new Error(`잘못된 userId:${userId} 입니다.`) } + const list = noticeReads.ReadAssets.map(ra => ra.fk_notice_id.toString()); + await redisClient.sAdd(`user:noticeReads:${userId}`, list); + + return list } \ No newline at end of file diff --git a/controllers/common_method/validator.ts b/controllers/common_method/validator.ts index c5889f6..cc268bf 100644 --- a/controllers/common_method/validator.ts +++ b/controllers/common_method/validator.ts @@ -7,9 +7,9 @@ const modelMap = new Map([ ]); export interface IGenericUserRequest { - user_id?: number; - notice_id?: number; - event_id?: number; + user_id?: string; + notice_id?: string; + event_id?: string; } // Request body 검증 함수 @@ -36,7 +36,7 @@ export async function findObjectByPk(body: IGenericUserRequest) { const Model = modelMap.get(key); const object = await Model.findByPk(body[key]); if (!object) { - errors.push(`${key}에 해당하는 객체를 찾을 수 없습니다.`); + errors.push(`${body[key]}:${key}에 해당하는 객체를 찾을 수 없습니다.`); } } } diff --git a/controllers/event/event_bookmark.ts b/controllers/event/event_bookmark.ts index beb8f15..5b951e4 100644 --- a/controllers/event/event_bookmark.ts +++ b/controllers/event/event_bookmark.ts @@ -33,13 +33,13 @@ export const setBookmark = async ( // 북마크에 이미 추가되어 있는지 확인 let [bookmark, created] = await Bookmark.findOrCreate({ where: { fk_user_id: user_id }, - defaults: { fk_user_id: user_id }, + defaults: { fk_user_id: +user_id }, transaction, logging: console.log }) // BookmarkAsset 테이블에 항목 추가 await BookmarkAsset.create({ - fk_event_id: event_id, + fk_event_id: +event_id, fk_bookmark_id: bookmark.id, }, { transaction, ignoreDuplicates: true }); diff --git a/controllers/event/event_like.ts b/controllers/event/event_like.ts index 3f812ce..f5b0ba4 100644 --- a/controllers/event/event_like.ts +++ b/controllers/event/event_like.ts @@ -35,8 +35,8 @@ async function updateLikeStatus(body: IEventUserRequest) { }, defaults: { // 새로 생성될 때 사용할 기본 값들 like, - fk_event_id: event_id, - fk_user_id: user_id + fk_event_id: +event_id, + fk_user_id: +user_id }, transaction, logging: console.log diff --git a/controllers/event/request/request.ts b/controllers/event/request/request.ts index 2167e8c..847961b 100644 --- a/controllers/event/request/request.ts +++ b/controllers/event/request/request.ts @@ -1,5 +1,5 @@ export interface IEventUserRequest { - event_id: number; - user_id: number; + event_id: string; + user_id: string; like: "like" | "dislike" | null; } \ No newline at end of file diff --git a/controllers/jwt/jwt.ts b/controllers/jwt/jwt.ts index cf9c05d..3962b23 100644 --- a/controllers/jwt/jwt.ts +++ b/controllers/jwt/jwt.ts @@ -1,5 +1,6 @@ import * as Express from 'express'; import jwt from "jsonwebtoken"; +import { mode } from '../../adminPage/components/index.js'; import { redisClient } from '../../redis/connect.js'; const ACCESS_EXPIRY = '1d'; @@ -16,6 +17,7 @@ enum TokenType { } export const verifyToken = (req: Express.Request, res, next) => { + if (mode == 'development') next(); const token = req.cookies.accessToken; const id = req.cookies.id; if (!token) { diff --git a/controllers/notice/index.ts b/controllers/notice/index.ts index c8a6e64..a659e73 100644 --- a/controllers/notice/index.ts +++ b/controllers/notice/index.ts @@ -1,8 +1,9 @@ import { setLike } from "./notice_like.js"; -import { getUnread, setRead } from "./notice_read.js"; +import { getRead, setRead } from "./notice_read.js"; import { getAlertAll, getNotice, getNoticeAll } from "./notice_search.js"; export { getAlertAll, - getNotice, getNoticeAll, getUnread, setLike, setRead + getNotice, getNoticeAll, getRead, setLike, setRead }; + diff --git a/controllers/notice/notice_like.ts b/controllers/notice/notice_like.ts index cb23de6..1d5f4c3 100644 --- a/controllers/notice/notice_like.ts +++ b/controllers/notice/notice_like.ts @@ -35,8 +35,8 @@ async function updateLikeStatus(body: INoticeUserRequest) { }, defaults: { // 새로 생성될 때 사용할 기본 값들 like, - fk_notice_id: notice_id, - fk_user_id: user_id + fk_notice_id: +notice_id, + fk_user_id: +user_id }, transaction, logging: console.log diff --git a/controllers/notice/notice_read.ts b/controllers/notice/notice_read.ts index 1e041f1..4cba867 100644 --- a/controllers/notice/notice_read.ts +++ b/controllers/notice/notice_read.ts @@ -1,11 +1,9 @@ import * as express from "express"; -import { INoticeUserRequest } from "./request/request.js"; -import { findObjectByPk, findUser, validateRequestBody } from "../common_method/validator.js"; -import { Notice, Read, ReadAsset, sequelize } from "../../models/index.js"; -import { IRead, IReadAsset } from "../../models/types.js"; -import { Op } from "sequelize"; +import { Read, ReadAsset, sequelize } from "../../models/index.js"; import { redisClient } from "../../redis/connect.js"; import { getNoticeReadInfo } from "../common_method/user_information.js"; +import { findObjectByPk, validateRequestBody } from "../common_method/validator.js"; +import { INoticeUserRequest } from "./request/request.js"; const bodyList = [ "notice_id", @@ -15,8 +13,8 @@ const bodyList = [ const EXPIRE = 3600; // 유효시간 1시간 // GET /users/read -export const getUnread = async ( - { params, body }: express.Request, +export const getRead = async ( + { params, body }: express.Request, res: express.Response, next: any ) => { @@ -25,32 +23,11 @@ export const getUnread = async ( if (!validateRequestBody(body, ["user_id"])) { return res.status(404).json({ error: "잘못된 key 입니다." }); } - const user_id = body.user_id; - - // DB에서 유저 찾기 - await findUser(user_id); + const {user_id} = body; // 해당 사용자의 Read 항목 찾기 - const read = await Read.findOne({ where: { fk_user_id: user_id } }) as IRead | null; - - if (!read) { - // Read 항목이 없으면 모든 공지 반환 - const allNotices = await Notice.findAll(); - return res.status(200).json(allNotices); - } else { - // ReadAsset 테이블에서 읽은 공지 목록 찾기 - const readAssetsList = await ReadAsset.findAll({ - where: { fk_read_id: read.id }, - attributes: ['fk_notice_id'] - }) as IReadAsset[]; - const readNoticeIds = readAssetsList.map(asset => asset.fk_notice_id); - - // 읽지 않은 공지 찾기 - const unreadNotices = await Notice.findAll({ - where: { id: { [Op.notIn]: readNoticeIds } } - }); - return res.status(200).json(unreadNotices); - } + const read = await getNoticeReadInfo(user_id); + return res.status(200).json(read); } catch (error) { console.log(error); return res.status(500).json({ error: "서버 내부 에러" }); @@ -70,7 +47,6 @@ export const setRead = async ( return res.status(404).json({ error: "잘못된 key 입니다." }); } const { user_id, notice_id } = body; - // DB에서 공지와 유저 찾기 const errorMessage = await findObjectByPk(body); if (errorMessage) { @@ -78,39 +54,28 @@ export const setRead = async ( } // Read 테이블이 생성되어 있는지 확인 - let read = await Read.findOne({ where: { fk_user_id: user_id } }) as IRead | null; - - // Read 테이블 생성이 안되어 있다면 새로 생성 - if (!read) { - read = await Read.create({ fk_user_id: user_id }, { transaction }) as IRead; - } - - // 해당 ReadAsset 항목이 이미 있는지 확인 - const existingReadAsset = await ReadAsset.findOne({ - where: { - fk_notice_id: notice_id, - fk_read_id: read.id - } + const read = await Read.findOne({ + where: { fk_user_id: user_id }, + include: [{model: ReadAsset, as: "ReadAssets"}], + transaction }); - // ReadAsset 항목이 존재하지 않으면 추가 - if (!existingReadAsset) { - await ReadAsset.create({ - fk_notice_id: notice_id, - fk_read_id: read.id, - }, { transaction }); - } + // ReadAsset 항목 추가 + await ReadAsset.create({ + fk_notice_id: notice_id, + fk_read_id: read.id, + }, { transaction, ignoreDuplicates: true }); await transaction.commit(); - + const list = read.ReadAssets.map(ra => ra.fk_notice_id.toString()); + if (!list.includes(notice_id)) list.push(notice_id.toString()); // Redis에 있는 해당 사용자의 읽음 정보 업데이트 - const updatedReadAssets = await getNoticeReadInfo(user_id.toString()); - await redisClient.set(`user:read:${user_id}`, JSON.stringify(updatedReadAssets), { EX: EXPIRE }); + await redisClient.sAdd(`user:noticeReads:${user_id}`, list); return res.status(200).json({ message: "읽음 설정 성공했습니다." }); } catch (error) { - await transaction.rollback(); console.log(error); + await transaction.rollback(); return res.status(500).json({ error: "서버 내부 에러" }); } }; \ No newline at end of file diff --git a/controllers/notice/request/request.ts b/controllers/notice/request/request.ts index 6272171..e87350f 100644 --- a/controllers/notice/request/request.ts +++ b/controllers/notice/request/request.ts @@ -1,5 +1,5 @@ export interface INoticeUserRequest { - notice_id: number; - user_id: number; + notice_id: string; + user_id: string; like: 'like' | 'dislike' | null } \ No newline at end of file diff --git a/models/reads.ts b/models/reads.ts index b81265a..412125b 100644 --- a/models/reads.ts +++ b/models/reads.ts @@ -1,7 +1,8 @@ import { DataTypes } from 'sequelize'; import { sequelize } from './sequelize.js'; +import { IRead } from './types.js'; -const Read = sequelize.define('Read', { +const Read = sequelize.define('Read', { id: { type: DataTypes.INTEGER.UNSIGNED, primaryKey: true, diff --git a/models/reads_assets.ts b/models/reads_assets.ts index 13596b2..fd52e67 100644 --- a/models/reads_assets.ts +++ b/models/reads_assets.ts @@ -1,8 +1,9 @@ import { DataTypes } from 'sequelize'; -import { sequelize } from './sequelize.js'; import Read from './reads.js'; +import { sequelize } from './sequelize.js'; +import { IReadAsset } from './types.js'; -const ReadAsset = sequelize.define('Read', { +const ReadAsset = sequelize.define('Read', { id: { type: DataTypes.INTEGER.UNSIGNED, primaryKey: true, @@ -40,5 +41,5 @@ const ReadAsset = sequelize.define('Read', { }); Read.hasMany(ReadAsset, { as: 'ReadAssets', foreignKey: 'fk_read_id' }); - +ReadAsset.belongsTo(Read, {foreignKey: 'fk_read_id'}); export default ReadAsset; diff --git a/models/types.d.ts b/models/types.d.ts index d08aa87..650c628 100644 --- a/models/types.d.ts +++ b/models/types.d.ts @@ -80,11 +80,12 @@ export interface IBookmarkAsset { export interface IRead extends Model { id: number; fk_user_id: number; + ReadAssets?: IReadAsset[]; } export interface IReadAsset extends Model { - id?: number; - fk_notice_id?: number; - fk_read_id?: number; + id: number; + fk_notice_id: number; + fk_read_id: number; } diff --git a/routes/notice.ts b/routes/notice.ts index 33ab0bf..bffe429 100644 --- a/routes/notice.ts +++ b/routes/notice.ts @@ -14,7 +14,7 @@ noticeRouter.get('/posts/notices/:noticeId', noticeController.getNotice); noticeRouter.get('/posts/alerts/:noticeId', noticeController.getNotice); /** 읽음 표시 */ -noticeRouter.get('/users/read', verifyToken, noticeController.getUnread); +noticeRouter.get('/users/read', verifyToken, noticeController.getRead); noticeRouter.post('/users/read', verifyToken, noticeController.setRead); export default noticeRouter; \ No newline at end of file