diff --git a/config/middlewares.js b/config/middlewares.js index 2786452..220d6ab 100644 --- a/config/middlewares.js +++ b/config/middlewares.js @@ -9,60 +9,68 @@ import { response } from "./response"; require("dotenv").config(); const format = () => { - const result = process.env.NODE_ENV === "production" ? "combined" : "dev"; - return result; + const result = process.env.NODE_ENV === "production" ? "combined" : "dev"; + return result; }; // 로그 작성을 위한 Output stream옵션. const stream = { - write: (message) => { - logger.info(message.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "")); - }, + write: (message) => { + logger.info( + message.replace( + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + "", + ), + ); + }, }; const skip = (_, res) => { - if (process.env.NODE_ENV === "production") { - return res.ststusCode < 400; - } - return false; + if (process.env.NODE_ENV === "production") { + return res.ststusCode < 400; + } + return false; }; const multerS3Uploader = multerS3({ - s3, - bucket: "bookjam-dev-s3", - acl: "public-read", - contentType: multerS3.AUTO_CONTENT_TYPE, - key: (req, file, cb) => cb(null, `${Date.now()}_${file.originalname}`), + s3, + bucket: "bookjam-dev-s3", + acl: "public-read", + contentType: multerS3.AUTO_CONTENT_TYPE, + key: (req, file, cb) => cb(null, `${Date.now()}_${file.originalname}`), }); const middlewares = { - s3Upload: multer({ - dest: "uploads/images/", - limits: { - fieldSize: 5 * 1024 * 1024, - }, - storage: multerS3Uploader, - }), + s3Upload: multer({ + dest: "uploads/images/", + limits: { + fieldSize: 5 * 1024 * 1024, + }, + storage: multerS3Uploader, + }), - logger: morgan(format(), { stream, skip }), + logger: morgan(format(), { stream, skip }), - authCheck: async (req, res, next) => { - const token = jwt.extractTokenFromHeader(req); + authCheck: async (req, res, next) => { + const token = jwt.extractTokenFromHeader(req); - if (!token) { - return res.status(401).json(baseResponse.JWT_TOKEN_EMPTY); - } + if (!token) { + return res.status(401).json(baseResponse.JWT_TOKEN_EMPTY); + } - try { - const payload = await jwt.verifyTokenAsync(token); - req.user = payload; - next(); - } catch (error) { - logger.error(error.message); - if (error.name === "TokenExpiredError") return res.status(401).json(baseResponse.ACCESS_TOKEN_EXPIRED); - return res.status(401).json(response(baseResponse.JWT_VERIFICATION_FAILED)); - } - }, + try { + const payload = await jwt.verifyTokenAsync(token); + req.user = payload; + next(); + } catch (error) { + logger.error(error.message); + if (error.name === "TokenExpiredError") + return res.status(401).json(baseResponse.ACCESS_TOKEN_EXPIRED); + return res + .status(401) + .json(response(baseResponse.JWT_VERIFICATION_FAILED)); + } + }, }; export default middlewares; diff --git a/src/places/placesController.js b/src/places/placesController.js index fa63516..f858985 100644 --- a/src/places/placesController.js +++ b/src/places/placesController.js @@ -7,221 +7,284 @@ const CATEGORY_KEYWORD = ["0", "1", "2"]; const SORT_BY_KEYWORD = ["rating", "review", "distance"]; const placesController = { - searchPlaces: async (req, res) => { - try { - const { - query: { keyword, sortBy = "rating", lat, lon, last }, - } = req; - - if (!keyword) { - return res.status(400).json(response(baseResponse.SEARCH_KEYWORD_EMPTY)); - } - - if (!SORT_BY_KEYWORD.includes(sortBy)) { - return res.status(400).json(response(baseResponse.SORT_BY_UNAVAILABLE)); - } - - if (sortBy === "distance" && !(lat && lon)) { - return res.status(400).json(response(baseResponse.LOCATION_EMPTY)); - } - - const searchResults = await placesService.searchPlaces(keyword, sortBy, { lat, lon }, last); - - return res.status(200).json(response(baseResponse.SUCCESS, searchResults)); - } catch (error) { - logger.error(error.message); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - getReviews: async (req, res) => { - try { - const { - params: { placeId }, - query: { last }, - } = req; - - const reviews = await placesService.findReviews(placeId, last); - - if (reviews.error) { - return res.status(404).json(response(baseResponse.PLACE_NOT_FOUND)); - } - - return res.status(200).json(response(baseResponse.SUCCESS, reviews.result)); - } catch (error) { - console.log(error); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - postReview: async (req, res) => { - try { - const { - params: { placeId }, - body: { visitedAt, contents, rating }, - user: { userId: author }, - } = req; - - const review = { author, placeId, visitedAt, contents, rating }; - - const result = await placesService.addReview(review); - - if (result.error) { - return res.status(400).json(response(baseResponse.PLACE_NOT_FOUND)); - } - - return res.status(201).json( - response(baseResponse.SUCCESS, { - reviewId: result.reviewId, - }) - ); - } catch (error) { - logger.error(error); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - getPlaces: async (req, res) => { - try { - const { - query: { category, sortBy = "distance", lat, lon, last }, - } = req; - - if (!CATEGORY_KEYWORD.includes(category)) { - console.log(category); - return res.status(400).json(response(baseResponse.CATEGORY_UNAVAILABLE)); - } - - if (!SORT_BY_KEYWORD.includes(sortBy)) { - return res.status(400).json(response(baseResponse.SORT_BY_UNAVAILABLE)); - } - - if (sortBy === "distance" && !(lat || lon)) { - return res.status(400).json(response(baseResponse.LOCATION_EMPTY)); - } - - const places = await placesService.getPlacesByCategory(category, sortBy, { lat, lon }, last); - - return res.status(200).json(response(baseResponse.SUCCESS, places)); - } catch (error) { - console.log(error); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - getActivities: async (req, res) => { - try { - const placeId = req.params.placeId; - - const activities = await placesService.retrieveActivitiesByPlaceId(placeId); - - if (activities.error) { - return res.status(400).json(response(baseResponse.LOCATION_EMPTY)); - } - return res.status(200).json(response(baseResponse.SUCCESS, activities)); - } catch (error) { - //console.log(error); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - getNews: async (req, res) => { - try { - const placeId = req.params.placeId; - const news = await placesService.retrieveNewsByPlaceId(placeId); - - if (news.error) { - if (news.cause == "place") { - return res.status(400).json(response(baseResponse.LOCATION_EMPTY)); - } - if (news.cause == "news") { - return res.status(400).json(response(baseResponse.NEWS_NOT_FOUND)); - } - } - return res.status(200).json(response(baseResponse.SUCCESS, news)); - } catch (error) { - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - getBooks: async (req, res) => { - try { - const placeId = req.params.placeId; - const books = await placesService.retrieveBooksByPlaceId(placeId); - - if (books.error) { - if (books.cause == "place") { - return res.status(400).json(response(baseResponse.LOCATION_EMPTY)); - } - if (books.cause == "books") { - return res.status(400).json(response(baseResponse.BOOKS_NOT_FOUND)); - } - } - return res.status(200).json(response(baseResponse.SUCCESS, books)); - } catch (error) { - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - - getPlaceDetails: async (req, res) => { - try { - const { - params: { placeId }, - user: { userId }, - } = req; - - const details = await placesService.getPlaceDetails(placeId, userId); - - if (details.error) { - if (details.error.name == "PlaceNotFound") { - return res.status(404).json(response(baseResponse.PLACE_NOT_FOUND)); - } - } - - return res.status(200).json(response(baseResponse.SUCCESS, details.result)); - } catch (error) { - console.log(error); - logger.error(error); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - - postBookmark: async (req, res) => { - try { - const userId = req.user.userId; - const placeId = req.params.placeId; - if (!placeId) return res.status(404).json(response(baseResponse.PLACE_ID_NOT_FOUND)); - const result = await placesService.postBookmark(placeId, userId); - if (result.error) { - if (result.error.name == "PlaceNotFound") { - return res.status(404).json(response(baseResponse.PLACE_NOT_FOUND)); - } - return res.status(400).json(response(baseResponse.CANNOT_BOOKMARK)); - } - return res.status(200).json(response(baseResponse.SUCCESS, "Bookmark Success")); - } catch (error) { - console.log(error); - logger.error(error); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, - - deleteBookmark: async (req, res) => { - try { - const userId = req.user.userId; - const placeId = req.params.placeId; - if (!placeId) return res.status(404).json(response(baseResponse.PLACE_ID_NOT_FOUND)); - const result = await placesService.deleteBookmark(placeId, userId); - if (result.error) { - if (result.error == "PlaceNotFound") { - return res.status(404).json(response(baseResponse.PLACE_NOT_FOUND)); - } - if (result.error === "Not Bookmark") { - return res.status(404).json(response(baseResponse.BOOKMARK_NOT_FOUND)); - } - return res.status(400).json(response(baseResponse.CANNOT_UNBOOKMARK)); - } - return res.status(200).json(response(baseResponse.SUCCESS, "Un-Bookmark Success")); - } catch (error) { - console.log(error); - logger.error(error); - return res.status(500).json(response(baseResponse.SERVER_ERROR)); - } - }, + searchPlaces: async (req, res) => { + try { + const { + query: { keyword, sortBy = "rating", lat, lon, last }, + } = req; + + if (!keyword) { + return res + .status(400) + .json(response(baseResponse.SEARCH_KEYWORD_EMPTY)); + } + + if (!SORT_BY_KEYWORD.includes(sortBy)) { + return res + .status(400) + .json(response(baseResponse.SORT_BY_UNAVAILABLE)); + } + + if (sortBy === "distance" && !(lat && lon)) { + return res + .status(400) + .json(response(baseResponse.LOCATION_EMPTY)); + } + + const searchResults = await placesService.searchPlaces( + keyword, + sortBy, + { lat, lon }, + last, + ); + + return res + .status(200) + .json(response(baseResponse.SUCCESS, searchResults)); + } catch (error) { + logger.error(error.message); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + getReviews: async (req, res) => { + try { + const { + params: { placeId }, + query: { last }, + } = req; + + const reviews = await placesService.findReviews(placeId, last); + + if (reviews.error) { + return res + .status(404) + .json(response(baseResponse.PLACE_NOT_FOUND)); + } + + return res + .status(200) + .json(response(baseResponse.SUCCESS, reviews.result)); + } catch (error) { + console.log(error); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + postReview: async (req, res) => { + try { + const { + params: { placeId }, + body: { visitedAt, contents, rating }, + user: { userId: author }, + } = req; + + const review = { author, placeId, visitedAt, contents, rating }; + + const result = await placesService.addReview(review); + + if (result.error) { + return res + .status(400) + .json(response(baseResponse.PLACE_NOT_FOUND)); + } + + return res.status(201).json( + response(baseResponse.SUCCESS, { + reviewId: result.reviewId, + }), + ); + } catch (error) { + logger.error(error); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + getPlaces: async (req, res) => { + try { + const { + query: { sortBy = "distance", lat, lon, last }, + } = req; + + if (!SORT_BY_KEYWORD.includes(sortBy)) { + return res + .status(400) + .json(response(baseResponse.SORT_BY_UNAVAILABLE)); + } + + if (sortBy === "distance" && !(lat || lon)) { + return res + .status(400) + .json(response(baseResponse.LOCATION_EMPTY)); + } + + const places = await placesService.getPlacesByCategory( + sortBy, + { lat, lon }, + last, + ); + + return res.status(200).json(response(baseResponse.SUCCESS, places)); + } catch (error) { + console.log(error); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + getActivities: async (req, res) => { + try { + const placeId = req.params.placeId; + + const activities = await placesService.retrieveActivitiesByPlaceId( + placeId, + ); + + if (activities.error) { + return res + .status(400) + .json(response(baseResponse.LOCATION_EMPTY)); + } + return res + .status(200) + .json(response(baseResponse.SUCCESS, activities)); + } catch (error) { + //console.log(error); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + getNews: async (req, res) => { + try { + const placeId = req.params.placeId; + const news = await placesService.retrieveNewsByPlaceId(placeId); + + if (news.error) { + if (news.cause == "place") { + return res + .status(400) + .json(response(baseResponse.LOCATION_EMPTY)); + } + if (news.cause == "news") { + return res + .status(400) + .json(response(baseResponse.NEWS_NOT_FOUND)); + } + } + return res.status(200).json(response(baseResponse.SUCCESS, news)); + } catch (error) { + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + getBooks: async (req, res) => { + try { + const placeId = req.params.placeId; + const books = await placesService.retrieveBooksByPlaceId(placeId); + + if (books.error) { + if (books.cause == "place") { + return res + .status(400) + .json(response(baseResponse.LOCATION_EMPTY)); + } + if (books.cause == "books") { + return res + .status(400) + .json(response(baseResponse.BOOKS_NOT_FOUND)); + } + } + return res.status(200).json(response(baseResponse.SUCCESS, books)); + } catch (error) { + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + + getPlaceDetails: async (req, res) => { + try { + const { + params: { placeId }, + user: { userId }, + } = req; + + const details = await placesService.getPlaceDetails( + placeId, + userId, + ); + + if (details.error) { + if (details.error.name == "PlaceNotFound") { + return res + .status(404) + .json(response(baseResponse.PLACE_NOT_FOUND)); + } + } + + return res + .status(200) + .json(response(baseResponse.SUCCESS, details.result)); + } catch (error) { + console.log(error); + logger.error(error); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + + postBookmark: async (req, res) => { + try { + const userId = req.user.userId; + const placeId = req.params.placeId; + if (!placeId) + return res + .status(404) + .json(response(baseResponse.PLACE_ID_NOT_FOUND)); + const result = await placesService.postBookmark(placeId, userId); + if (result.error) { + if (result.error.name == "PlaceNotFound") { + return res + .status(404) + .json(response(baseResponse.PLACE_NOT_FOUND)); + } + return res + .status(400) + .json(response(baseResponse.CANNOT_BOOKMARK)); + } + return res + .status(200) + .json(response(baseResponse.SUCCESS, "Bookmark Success")); + } catch (error) { + console.log(error); + logger.error(error); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, + + deleteBookmark: async (req, res) => { + try { + const userId = req.user.userId; + const placeId = req.params.placeId; + if (!placeId) + return res + .status(404) + .json(response(baseResponse.PLACE_ID_NOT_FOUND)); + const result = await placesService.deleteBookmark(placeId, userId); + if (result.error) { + if (result.error == "PlaceNotFound") { + return res + .status(404) + .json(response(baseResponse.PLACE_NOT_FOUND)); + } + if (result.error === "Not Bookmark") { + return res + .status(404) + .json(response(baseResponse.BOOKMARK_NOT_FOUND)); + } + return res + .status(400) + .json(response(baseResponse.CANNOT_UNBOOKMARK)); + } + return res + .status(200) + .json(response(baseResponse.SUCCESS, "Un-Bookmark Success")); + } catch (error) { + console.log(error); + logger.error(error); + return res.status(500).json(response(baseResponse.SERVER_ERROR)); + } + }, }; export default placesController; diff --git a/src/places/placesDao.js b/src/places/placesDao.js index 14dd923..4ea8916 100644 --- a/src/places/placesDao.js +++ b/src/places/placesDao.js @@ -1,6 +1,6 @@ const placesDao = { - selectPlacesByRegExp: async (regexp, sortBy, coord, last, connection) => { - let sql = ` + selectPlacesByRegExp: async (regexp, sortBy, coord, last, connection) => { + let sql = ` select p.place_id "placeId", p.name, p.category, a.road, a.jibun, p.review_count "reviewCount", cast(round(rating / p.review_count, 2) as float) rating, p.lat, p.lon from places p join (select place_id, sum(rating) as rating from place_reviews group by place_id) pr on pr.place_id = p.place_id @@ -8,41 +8,41 @@ const placesDao = { where name regexp '${regexp}' or a.jibun regexp '${regexp}' or a.road regexp '${regexp}' `; - let sortColumn; - if (sortBy === "rating") { - sortColumn = "rating"; - } else if (sortBy === "review") { - sortColumn = "reviewCount"; - } else if (sortBy === "distance") { - sortColumn = `st_distance_sphere(point(${coord.lon}, ${coord.lat}), point(lon, lat))`; - } - - const operator = sortBy === "distance" ? ">=" : "<="; - const order = sortBy === "distance" ? "asc" : "desc"; - - if (last) { - sql += ` and ${sortColumn} ${operator} (select ${sortColumn} from places where place_id = ${last})`; - } - sql += ` order by ${sortColumn} ${order}`; - sql += " limit 10"; - - const [queryResult] = await connection.query(sql); - - return queryResult; - }, - selectPlaceHoursByDay: async (placeId, day, connection) => { - const sql = ` + let sortColumn; + if (sortBy === "rating") { + sortColumn = "rating"; + } else if (sortBy === "review") { + sortColumn = "reviewCount"; + } else if (sortBy === "distance") { + sortColumn = `st_distance_sphere(point(${coord.lon}, ${coord.lat}), point(lon, lat))`; + } + + const operator = sortBy === "distance" ? ">=" : "<="; + const order = sortBy === "distance" ? "asc" : "desc"; + + if (last) { + sql += ` and ${sortColumn} ${operator} (select ${sortColumn} from places where place_id = ${last})`; + } + sql += ` order by ${sortColumn} ${order}`; + sql += " limit 10"; + + const [queryResult] = await connection.query(sql); + + return queryResult; + }, + selectPlaceHoursByDay: async (placeId, day, connection) => { + const sql = ` select start_time "startTime", end_time "endTime" from place_hours where place_id = ${placeId} and day = ${day} `; - const [queryResult] = await connection.query(sql); + const [queryResult] = await connection.query(sql); - return queryResult; - }, - selectPlaceImages: async (placeId, connection) => { - const sql = ` + return queryResult; + }, + selectPlaceImages: async (placeId, connection) => { + const sql = ` select id, image_url url from place_reviews r join place_review_images i on r.review_id = i.review_id @@ -51,109 +51,108 @@ const placesDao = { limit 5 `; - const [queryResult] = await connection.query(sql); + const [queryResult] = await connection.query(sql); - return queryResult; - }, - selectPlaceById: async (placeId, connection) => { - const sql = ` + return queryResult; + }, + selectPlaceById: async (placeId, connection) => { + const sql = ` select place_id, name from places where place_id = ${placeId} `; - const [queryResult] = await connection.query(sql); + const [queryResult] = await connection.query(sql); - return queryResult; - }, - selectPlaceReviews: async (placeId, last, connection) => { - let sql = ` + return queryResult; + }, + selectPlaceReviews: async (placeId, last, connection) => { + let sql = ` select review_id "reviewId", visited_at "visitedAt", contents, rating, user_id "userId", username, profile_image "profileImage" from place_reviews r join users u on r.author = u.user_id where r.place_id = ${placeId} `; - if (last) { - sql += `and r.created_at < + if (last) { + sql += `and r.created_at < (select created_at from place_reviews where review_id = ${last})`; - } - sql += ` + } + sql += ` limit 5 `; - const [queryResult] = await connection.query(sql); + const [queryResult] = await connection.query(sql); - return queryResult; - }, - selectReviewImages: async (reviewId, connection) => { - const sql = ` + return queryResult; + }, + selectReviewImages: async (reviewId, connection) => { + const sql = ` select id, image_url url from place_review_images where review_id = ${reviewId} `; - const [queryResult] = await connection.query(sql); + const [queryResult] = await connection.query(sql); - return queryResult; - }, - insertReview: async (review, connection) => { - try { - const { author, placeId, visitedAt, contents, rating } = review; + return queryResult; + }, + insertReview: async (review, connection) => { + try { + const { author, placeId, visitedAt, contents, rating } = review; - const insertReviewSql = ` + const insertReviewSql = ` insert into place_reviews(author, visited_at, place_id, contents, rating) values (${author}, "${visitedAt}", ${placeId}, '${contents}', ${rating}) `; - await connection.beginTransaction(); + await connection.beginTransaction(); - const [queryResult] = await connection.query(insertReviewSql); + const [queryResult] = await connection.query(insertReviewSql); - const { insertId: reviewId } = queryResult; + const { insertId: reviewId } = queryResult; - await connection.commit(); + await connection.commit(); - return reviewId; - } catch (error) { - await connection.rollback(); - console.log(error); - } - }, - selectPlacesByCategory: async (category, sortBy, coord, last, connection) => { - let sql = ` - select p.place_id "placeId", p.name, convert(ifnull(round(rating / p.review_count, 2),0), float) rating, p.review_count "reviewCount", p.category, a.road, a.jibun, p.lat, p.lon + return reviewId; + } catch (error) { + await connection.rollback(); + console.log(error); + } + }, + selectPlacesByCategory: async (sortBy, coord, last, connection) => { + let sql = ` + select p.place_id "placeId", p.name, convert(ifnull(round(rating / p.review_count, 2),0), float) rating, p.review_count "reviewCount", a.road, a.jibun, p.lat, p.lon from places p join place_address a on p.place_id = a.place_id left join (select place_id, sum(rating) as rating from place_reviews group by place_id) pr on pr.place_id = p.place_id - where p.category = ${category} `; - let sortColumn; - if (sortBy === "rating") { - sortColumn = "rating"; - } else if (sortBy === "review") { - sortColumn = "review_count"; - } else if (sortBy === "distance") { - sortColumn = `st_distance_sphere(point(${coord.lon}, ${coord.lat}), point(lon, lat))`; - } - - const operator = sortBy === "distance" ? ">=" : "<="; - const order = sortBy === "distance" ? "asc" : "desc"; - - if (last) { - sql += ` and ${sortColumn} ${operator} (select ${sortColumn} from places where category = ${category} and place_id = ${last})`; - } - sql += ` order by ${sortColumn} ${order}`; - sql += " limit 10"; - - const [queryResult] = await connection.query(sql); - - return queryResult; - }, - selectActivitiesByPlaceId: async (connection, placeId) => { - const sql = ` + let sortColumn; + if (sortBy === "rating") { + sortColumn = "rating"; + } else if (sortBy === "review") { + sortColumn = "review_count"; + } else if (sortBy === "distance") { + sortColumn = `st_distance_sphere(point(${coord.lon}, ${coord.lat}), point(lon, lat))`; + } + + const operator = sortBy === "distance" ? ">=" : "<"; + const order = sortBy === "distance" ? "asc" : "desc"; + + if (last) { + sql += ` where ${sortColumn} ${operator} (select ${sortColumn} from places where place_id = ${last}) or (p.place_id > ${last} and ${sortColumn} = (select ${sortColumn} from places where place_id = ${last}))`; + } + sql += ` order by st_distance_sphere(point(${coord.lon}, ${coord.lat}), point(lon, lat)), ${sortColumn} ${order}`; + sql += " limit 10"; + console.log(sql); + const [queryResult] = await connection.query(sql); + + return queryResult; + }, + selectActivitiesByPlaceId: async (connection, placeId) => { + const sql = ` SELECT activity_id "activityId", created_at "createdAt", updated_at "updatedAt", @@ -165,33 +164,33 @@ const placesDao = { WHERE place_id = ?; `; - const [queryActivities] = await connection.query(sql, placeId); - - return queryActivities; - }, - checkPlace: async (connection, placeId) => { - const sql = `SELECT count(*) as c FROM places WHERE place_id = ${placeId}`; - try { - const [[records]] = await connection.query(sql); - return records.c; - } catch (error) { - console.log(error); - return { error: true }; - } - }, - selectNewsByPlaceId: async (connection, placeId) => { - const sql = `SELECT news_id "newsId", created_at "createdAt", updated_at "updatedcAt", title, contents, place_id "placeId" FROM place_news WHERE place_id = ${placeId} ORDER BY created_at DESC LIMIT 10;`; - const [queryResult] = await connection.query(sql); - return queryResult; - }, - selectBooksByPlaceId: async (connection, placeId) => { - const sql = `SELECT id, place_id "placeId", isbn, created_at "createAt" FROM place_books WHERE place_id = ${placeId} ORDER BY created_at DESC LIMIT 5;`; - - const [queryResult] = await connection.query(sql); - return queryResult; - }, - selectPlaceDetails: async (placeId, connection) => { - const sql = ` + const [queryActivities] = await connection.query(sql, placeId); + + return queryActivities; + }, + checkPlace: async (connection, placeId) => { + const sql = `SELECT count(*) as c FROM places WHERE place_id = ${placeId}`; + try { + const [[records]] = await connection.query(sql); + return records.c; + } catch (error) { + console.log(error); + return { error: true }; + } + }, + selectNewsByPlaceId: async (connection, placeId) => { + const sql = `SELECT news_id "newsId", created_at "createdAt", updated_at "updatedcAt", title, contents, place_id "placeId" FROM place_news WHERE place_id = ${placeId} ORDER BY created_at DESC LIMIT 10;`; + const [queryResult] = await connection.query(sql); + return queryResult; + }, + selectBooksByPlaceId: async (connection, placeId) => { + const sql = `SELECT id, place_id "placeId", isbn, created_at "createAt" FROM place_books WHERE place_id = ${placeId} ORDER BY created_at DESC LIMIT 5;`; + + const [queryResult] = await connection.query(sql); + return queryResult; + }, + selectPlaceDetails: async (placeId, connection) => { + const sql = ` select p.place_id "placeId", p.name, @@ -207,49 +206,49 @@ const placesDao = { where p.place_id = ${placeId} `; - const queryResult = await connection.query(sql); + const queryResult = await connection.query(sql); - return queryResult; - }, - checkPlaceBookmarked: async (placeId, userId, connection) => { - const sql = ` + return queryResult; + }, + checkPlaceBookmarked: async (placeId, userId, connection) => { + const sql = ` select id from place_bookmarks where place_id = ${placeId} and bookmarker = ${userId} `; - const [queryResult] = await connection.query(sql); - - return queryResult; - }, - insertBookmark: async (placeId, userId, connection) => { - const sql = `INSERT INTO place_bookmarks (place_id, bookmarker) VALUES (${placeId}, ${userId})`; - try { - await connection.beginTransaction(); - const [result] = await connection.query(sql); - await connection.commit(); - return result; - } catch (error) { - await connection.rollback(); - return { error: true }; - } - }, - - deleteBookmark: async (placeId, userId, connection) => { - const chk = `select * from place_bookmarks where bookmarker = ${userId} and place_id = ${placeId}`; - const sql = `DELETE FROM place_bookmarks WHERE place_id = ${placeId} and bookmarker = ${userId}`; - try { - const [[check]] = await connection.query(chk); - if (!check) return { error: "Not Bookmark" }; - await connection.beginTransaction(); - const [result] = await connection.query(sql); - await connection.commit(); - return result; - } catch (error) { - await connection.rollback(); - return { error: true }; - } - }, + const [queryResult] = await connection.query(sql); + + return queryResult; + }, + insertBookmark: async (placeId, userId, connection) => { + const sql = `INSERT INTO place_bookmarks (place_id, bookmarker) VALUES (${placeId}, ${userId})`; + try { + await connection.beginTransaction(); + const [result] = await connection.query(sql); + await connection.commit(); + return result; + } catch (error) { + await connection.rollback(); + return { error: true }; + } + }, + + deleteBookmark: async (placeId, userId, connection) => { + const chk = `select * from place_bookmarks where bookmarker = ${userId} and place_id = ${placeId}`; + const sql = `DELETE FROM place_bookmarks WHERE place_id = ${placeId} and bookmarker = ${userId}`; + try { + const [[check]] = await connection.query(chk); + if (!check) return { error: "Not Bookmark" }; + await connection.beginTransaction(); + const [result] = await connection.query(sql); + await connection.commit(); + return result; + } catch (error) { + await connection.rollback(); + return { error: true }; + } + }, }; export default placesDao; diff --git a/src/places/placesService.js b/src/places/placesService.js index 6d54d58..455cff1 100644 --- a/src/places/placesService.js +++ b/src/places/placesService.js @@ -4,288 +4,365 @@ import pool from "../../config/database"; import placesDao from "./placesDao"; const getTime = async () => { - const curr = new Date(); - const utc = curr.getTime() + curr.getTimezoneOffset() * 60 * 1000; - const KR_TIME_DIFF = 9 * 60 * 60 * 1000; - const krCurr = new Date(utc + KR_TIME_DIFF); - return krCurr; + const curr = new Date(); + const utc = curr.getTime() + curr.getTimezoneOffset() * 60 * 1000; + const KR_TIME_DIFF = 9 * 60 * 60 * 1000; + const krCurr = new Date(utc + KR_TIME_DIFF); + return krCurr; }; const checkOpen = async (hours, krCurr) => { - if (!hours) { - return null; - } - - const { startTime, endTime } = hours; - const baseTime = `${krCurr.getFullYear()}-${krCurr.getMonth() + 1}-${krCurr.getDate()}`; - const startTimeDate = new Date(`${baseTime} ${startTime}`); - const endTimeDate = new Date(`${baseTime} ${endTime}`); - - const open = startTimeDate.getTime() <= krCurr.getTime() && krCurr.getTime() <= endTimeDate.getTime(); - - return open; + if (!hours) { + return null; + } + + const { startTime, endTime } = hours; + const baseTime = `${krCurr.getFullYear()}-${ + krCurr.getMonth() + 1 + }-${krCurr.getDate()}`; + const startTimeDate = new Date(`${baseTime} ${startTime}`); + const endTimeDate = new Date(`${baseTime} ${endTime}`); + + const open = + startTimeDate.getTime() <= krCurr.getTime() && + krCurr.getTime() <= endTimeDate.getTime(); + + return open; }; const placesService = { - searchPlaces: async (keyword, sortBy, coord, last) => { - const connection = await pool.getConnection(); - - const koreanRegExp = getRegExp(keyword).toString().slice(1, -2); - - let searchResult = await placesDao.selectPlacesByRegExp(koreanRegExp, sortBy, coord, last, connection); - - searchResult = await Promise.all( - searchResult.map(async (place) => { - place.reviewCount = Number(place.reviewCount); - const { road, jibun, lat, lon, ...rest } = place; - - const images = await placesDao.selectPlaceImages(place.placeId, connection); - - return { - ...rest, - images, - address: { - road, - jibun, - }, - coords: { - lat, - lon, - }, - }; - }) - ); - - connection.release(); - - return searchResult; - }, - findReviews: async (placeId, last) => { - const connection = await pool.getConnection(); - - const [placeExists] = await placesDao.selectPlaceById(placeId, connection); - - if (!placeExists) { - return { error: true }; - } - - let reviews = await placesDao.selectPlaceReviews(placeId, last, connection); - reviews = await Promise.all( - reviews.map(async (review) => { - const images = await placesDao.selectReviewImages(review.reviewId, connection); - const { userId, username, profileImage, ...rest } = review; - - return { - ...rest, - images, - author: { - userId, - username, - profileImage, - }, - }; - }) - ); - - connection.release(); - - return { - error: false, - result: reviews, - }; - }, - addReview: async (review) => { - const connection = await pool.getConnection(); - - const [placeExists] = await placesDao.selectPlaceById(review.placeId, connection); - if (!placeExists) { - return { error: true }; - } - - const reviewId = await placesDao.insertReview(review, connection); - connection.release(); - - return { error: false, reviewId }; - }, - getPlacesByCategory: async (category, sortBy, coord, last) => { - const connection = await pool.getConnection(); - - let searchResult = await placesDao.selectPlacesByCategory(category, sortBy, coord, last, connection); - - const curr = getTime(); - - searchResult = await Promise.all( - searchResult.map(async (place) => { - place.reviewCount = Number(place.reviewCount); - const [hours] = await placesDao.selectPlaceHoursByDay(place.placeId, curr.getDay(), connection); - const open = checkOpen(hours, curr); - - const images = await placesDao.selectPlaceImages(place.placeId, connection); - - const { road, jibun, lat, lon, ...rest } = place; - - return { - ...rest, - open, - images, - address: { - road, - jibun, - }, - coords: { - lat, - lon, - }, - }; - }) - ); - - connection.release(); - - return searchResult; - }, - retrieveActivitiesByPlaceId: async (placeId) => { - try { - const connection = await pool.getConnection(async (conn) => conn); - const activitiesResult = await placesDao.selectActivitiesByPlaceId(connection, placeId); - - if (Object.keys(activitiesResult).length == 0) { - return { error: true }; - } - connection.release(); - return { - error: false, - result: activitiesResult, - }; - } catch (err) { - return { error: true }; - } - }, - retrieveNewsByPlaceId: async (placeId) => { - try { - const connection = await pool.getConnection(async (conn) => conn); - const newsResult = await placesDao.selectNewsByPlaceId(connection, placeId); - const placeCnt = await placesDao.checkPlace(connection, placeId); - - connection.release(); - - if (placeCnt == 0) { - return { error: true, cause: "place" }; - } - if (Object.keys(newsResult).length == 0) { - return { error: true, cause: "news" }; - } - return newsResult; - } catch (error) { - return { error: true }; - } - }, - retrieveBooksByPlaceId: async (placeId) => { - try { - const connection = await pool.getConnection(async (conn) => conn); - const bookResult = await placesDao.selectBooksByPlaceId(connection, placeId); - const placeCnt = await placesDao.checkPlace(connection, placeId); - - connection.release(); - - if (placeCnt == 0) { - return { error: true, cause: "place" }; - } - if (Object.keys(bookResult).length == 0) { - return { error: true, cause: "books" }; - } - let results = []; - for (let book of bookResult) { - await axios({ - method: "GET", - url: `http://www.aladin.co.kr/ttb/api/ItemLookUp.aspx?ttbkey=${ - process.env.ALADIN_TTB - }&Version=20131101&itemIdType=ISBN13&ItemId=${Number(book.isbn)}&output=JS`, - responseType: "json", - }).then((res) => { - for (let book of res.data.item) { - const book_info = { - title: book.title, - author: book.author, - cover: book.cover, - description: book.description, - publisher: book.publisher, - isbn: book.isbn13, - }; - results.push(book_info); - } - }); - } - - return results; - } catch (error) { - return { error: true }; - } - }, - - getPlaceDetails: async (placeId, userId) => { - const connection = await pool.getConnection(); - - const [isPlaceExists] = await placesDao.selectPlaceById(placeId, connection); - - if (!isPlaceExists) { - return { error: { name: "PlaceNotFound" } }; - } - - const [details] = await placesDao.selectPlaceDetails(placeId, connection); - - const { jibun, road, ...rest } = details; - - let [bookmarked] = await placesDao.checkPlaceBookmarked(placeId, userId, connection); - bookmarked = bookmarked ? true : false; - - const images = await placesDao.selectPlaceImages(placeId, connection); - - const curr = await getTime(); - const [hours] = await placesDao.selectPlaceHoursByDay(placeId, curr.getDay(), connection); - const open = await checkOpen(hours, curr); - - connection.release(); - - return { - error: null, - result: { - ...rest, - address: { jibun, road }, - images, - open, - bookmarked, - }, - }; - }, - - postBookmark: async (placeId, userId) => { - try { - const connection = await pool.getConnection(); - const [isPlaceExists] = await placesDao.selectPlaceById(placeId, connection); - if (!isPlaceExists) { - return { error: { name: "PlaceNotFound" } }; - } - const result = await placesDao.insertBookmark(placeId, userId, connection); - return result; - } catch (error) { - return { error: true }; - } - }, - - deleteBookmark: async (placeId, userId) => { - try { - const connection = await pool.getConnection(); - const [isPlaceExists] = await placesDao.selectPlaceById(placeId, connection); - if (!isPlaceExists) { - return { error: "PlaceNotFound" }; - } - const result = await placesDao.deleteBookmark(placeId, userId, connection); - return result; - } catch (error) { - return { error: true }; - } - }, + searchPlaces: async (keyword, sortBy, coord, last) => { + const connection = await pool.getConnection(); + + const koreanRegExp = getRegExp(keyword).toString().slice(1, -2); + + let searchResult = await placesDao.selectPlacesByRegExp( + koreanRegExp, + sortBy, + coord, + last, + connection, + ); + + searchResult = await Promise.all( + searchResult.map(async (place) => { + place.reviewCount = Number(place.reviewCount); + const { road, jibun, lat, lon, ...rest } = place; + + const images = await placesDao.selectPlaceImages( + place.placeId, + connection, + ); + + return { + ...rest, + images, + address: { + road, + jibun, + }, + coords: { + lat, + lon, + }, + }; + }), + ); + + connection.release(); + + return searchResult; + }, + findReviews: async (placeId, last) => { + const connection = await pool.getConnection(); + + const [placeExists] = await placesDao.selectPlaceById( + placeId, + connection, + ); + + if (!placeExists) { + return { error: true }; + } + + let reviews = await placesDao.selectPlaceReviews( + placeId, + last, + connection, + ); + reviews = await Promise.all( + reviews.map(async (review) => { + const images = await placesDao.selectReviewImages( + review.reviewId, + connection, + ); + const { userId, username, profileImage, ...rest } = review; + + return { + ...rest, + images, + author: { + userId, + username, + profileImage, + }, + }; + }), + ); + + connection.release(); + + return { + error: false, + result: reviews, + }; + }, + addReview: async (review) => { + const connection = await pool.getConnection(); + + const [placeExists] = await placesDao.selectPlaceById( + review.placeId, + connection, + ); + if (!placeExists) { + return { error: true }; + } + + const reviewId = await placesDao.insertReview(review, connection); + connection.release(); + + return { error: false, reviewId }; + }, + getPlacesByCategory: async (sortBy, coord, last) => { + const connection = await pool.getConnection(); + + let searchResult = await placesDao.selectPlacesByCategory( + sortBy, + coord, + last, + connection, + ); + + const curr = await getTime(); + + searchResult = await Promise.all( + searchResult.map(async (place) => { + place.reviewCount = Number(place.reviewCount); + const [hours] = await placesDao.selectPlaceHoursByDay( + place.placeId, + curr.getDay(), + connection, + ); + const open = await checkOpen(hours, curr); + + const images = await placesDao.selectPlaceImages( + place.placeId, + connection, + ); + + const { road, jibun, lat, lon, ...rest } = place; + + return { + ...rest, + open, + images, + address: { + road, + jibun, + }, + coords: { + lat, + lon, + }, + }; + }), + ); + + connection.release(); + + return searchResult; + }, + retrieveActivitiesByPlaceId: async (placeId) => { + try { + const connection = await pool.getConnection(async (conn) => conn); + const activitiesResult = await placesDao.selectActivitiesByPlaceId( + connection, + placeId, + ); + + if (Object.keys(activitiesResult).length == 0) { + return { error: true }; + } + connection.release(); + return { + error: false, + result: activitiesResult, + }; + } catch (err) { + return { error: true }; + } + }, + retrieveNewsByPlaceId: async (placeId) => { + try { + const connection = await pool.getConnection(async (conn) => conn); + const newsResult = await placesDao.selectNewsByPlaceId( + connection, + placeId, + ); + const placeCnt = await placesDao.checkPlace(connection, placeId); + + connection.release(); + + if (placeCnt == 0) { + return { error: true, cause: "place" }; + } + if (Object.keys(newsResult).length == 0) { + return { error: true, cause: "news" }; + } + return newsResult; + } catch (error) { + return { error: true }; + } + }, + retrieveBooksByPlaceId: async (placeId) => { + try { + const connection = await pool.getConnection(async (conn) => conn); + const bookResult = await placesDao.selectBooksByPlaceId( + connection, + placeId, + ); + const placeCnt = await placesDao.checkPlace(connection, placeId); + + connection.release(); + + if (placeCnt == 0) { + return { error: true, cause: "place" }; + } + if (Object.keys(bookResult).length == 0) { + return { error: true, cause: "books" }; + } + let results = []; + for (let book of bookResult) { + await axios({ + method: "GET", + url: `http://www.aladin.co.kr/ttb/api/ItemLookUp.aspx?ttbkey=${ + process.env.ALADIN_TTB + }&Version=20131101&itemIdType=ISBN13&ItemId=${Number( + book.isbn, + )}&output=JS`, + responseType: "json", + }).then((res) => { + for (let book of res.data.item) { + const book_info = { + title: book.title, + author: book.author, + cover: book.cover, + description: book.description, + publisher: book.publisher, + isbn: book.isbn13, + }; + results.push(book_info); + } + }); + } + + return results; + } catch (error) { + return { error: true }; + } + }, + + getPlaceDetails: async (placeId, userId) => { + const connection = await pool.getConnection(); + + const [isPlaceExists] = await placesDao.selectPlaceById( + placeId, + connection, + ); + + if (!isPlaceExists) { + return { error: { name: "PlaceNotFound" } }; + } + + const [details] = await placesDao.selectPlaceDetails( + placeId, + connection, + ); + + const { jibun, road, ...rest } = details; + + let [bookmarked] = await placesDao.checkPlaceBookmarked( + placeId, + userId, + connection, + ); + bookmarked = bookmarked ? true : false; + + const images = await placesDao.selectPlaceImages(placeId, connection); + + const curr = await getTime(); + const [hours] = await placesDao.selectPlaceHoursByDay( + placeId, + curr.getDay(), + connection, + ); + const open = await checkOpen(hours, curr); + + connection.release(); + + return { + error: null, + result: { + ...rest, + address: { jibun, road }, + images, + open, + bookmarked, + }, + }; + }, + + postBookmark: async (placeId, userId) => { + try { + const connection = await pool.getConnection(); + const [isPlaceExists] = await placesDao.selectPlaceById( + placeId, + connection, + ); + if (!isPlaceExists) { + return { error: { name: "PlaceNotFound" } }; + } + const result = await placesDao.insertBookmark( + placeId, + userId, + connection, + ); + return result; + } catch (error) { + return { error: true }; + } + }, + + deleteBookmark: async (placeId, userId) => { + try { + const connection = await pool.getConnection(); + const [isPlaceExists] = await placesDao.selectPlaceById( + placeId, + connection, + ); + if (!isPlaceExists) { + return { error: "PlaceNotFound" }; + } + const result = await placesDao.deleteBookmark( + placeId, + userId, + connection, + ); + return result; + } catch (error) { + return { error: true }; + } + }, }; export default placesService; diff --git a/src/swagger/places.yaml b/src/swagger/places.yaml index dd29908..7a2d8d9 100644 --- a/src/swagger/places.yaml +++ b/src/swagger/places.yaml @@ -7,17 +7,11 @@ - bearerAuth: [] parameters: - in: query - name: category (0, 1, 2) - required: true - schema: - type: string - description: 카테고리 - - in: query - name: sortBy (["rating", "review", "distance"]) + name: sortBy required: false schema: type: string - description: 정렬 기준 + description: 정렬 기준 (["rating", "review", "distance"]) - in: query name: lat required: true @@ -58,8 +52,13 @@ "category": 0, "open": null, "images": [], - "address": { "road": "서울특별시 종로구 효자료 33", "jibun": null }, - "coords": { "lat": 85, "lon": 165 }, + "address": + { + "road": "서울특별시 종로구 효자료 33", + "jibun": null, + }, + "coords": + { "lat": 85, "lon": 165 }, }, { "placeId": 17, @@ -69,8 +68,13 @@ "category": 0, "open": null, "images": [], - "address": { "road": "경기도 수원시 정조로800번길 9", "jibun": null }, - "coords": { "lat": 85, "lon": 165 }, + "address": + { + "road": "경기도 수원시 정조로800번길 9", + "jibun": null, + }, + "coords": + { "lat": 85, "lon": 165 }, }, ], } @@ -137,8 +141,13 @@ "reviewCount": 1, "rating": 4.00, "images": [], - "address": { "road": "행복길", "jibun": "행복동" }, - "coords": { "lat": 85, "lon": 165 }, + "address": + { + "road": "행복길", + "jibun": "행복동", + }, + "coords": + { "lat": 85, "lon": 165 }, }, { "placeId": 1, @@ -148,8 +157,12 @@ "rating": 4.00, "images": [], "address": - { "road": "경기도 하남시 미사강변한강로 290-3", "jibun": null }, - "coords": { "lat": 85, "lon": 165 }, + { + "road": "경기도 하남시 미사강변한강로 290-3", + "jibun": null, + }, + "coords": + { "lat": 85, "lon": 165 }, }, ], } @@ -197,7 +210,12 @@ "contents": "너무 만족합니다", "rating": 4, "images": [], - "author": { "userId": 7, "username": "bookjam", "profileImage": null }, + "author": + { + "userId": 7, + "username": "bookjam", + "profileImage": null, + }, }, ], } @@ -248,7 +266,12 @@ schema: type: object example: - { "status": "SUCCESS", "code": 1000, "message": "성공", "result": { "reviewId": 14 } } + { + "status": "SUCCESS", + "code": 1000, + "message": "성공", + "result": { "reviewId": 14 }, + } "400": $ref: "./openapi.yaml#/components/responses/BadRequest" "500":