그저 내가 되었고

항해99) 4주차:: 주특기 숙련; 개인과제 전체코드(JS)(ver.Sequelize) 본문

개발/항해99 9기

항해99) 4주차:: 주특기 숙련; 개인과제 전체코드(JS)(ver.Sequelize)

hyuunii 2022. 10. 13. 07:50

사족

이걸 굳이 매번 다 올리는 이유는...

항해하면서 너무 힘들지만 절대 포기하고 싶지 않은 분들이 보시고 꼭 도움 받으시길 원하기 때문입니다.

제 뒤에 항해하시게 될 누군가가 만약 저처럼 너무너무 힘들어 하고 계시다면(T T) 그 마음을 너무 잘 알기에.. 제 마음도 많이 아플 것 같아요.

답지 보고 공부하는것도 도움이 많이 되더라고요. 잘 이해만 하면 돼요.

꼭, 이해하는것 절대로 포기하지 마시고 끝까지 완주해봅시다,,,,,(나도!!!!!!!!!!!!!!!!!!!!!!!)

 

 

app.js

더보기
const express = require("express");
const postsRouter = require("./routes/posts");
const commentsRouter = require("./routes/comments");
const signupRouter = require("./routes/signup");
const loginRouter = require("./routes/login");

const app = express();
const router = express.Router();

app.use(express.json());
// 👆🏻JSON 이라는 규격의 body 데이터를 손쉽게 사용할 수 있게 도와주는 미들웨어
app.use("/", express.urlencoded({ extended: false }), router);
// 👆🏻form-urlencoded 라는 규격의 body 데이터를 손쉽게 사용할 수 있게 도와주는 미들웨어
app.use("/", [postsRouter, commentsRouter, signupRouter, loginRouter]);

app.listen(8080, () => {
    console.log("서버가 요청을 받을 준비가 됐어요");
});

 

middlewares/auth-middleware

더보기
const jwt = require("jsonwebtoken");
const {Users} = require("../models"); //⭐

module.exports = async (req, res, next) => {
    const { authorization } = req.headers;
    // console.log(authorization)
    const [ authType, authToken ] = (authorization || "").split(" ");
    //console.log(authToken)

    if  (!authToken || authType !== "Bearer" ) {
        res.status(401).send({
            errorMessage: "로그인이 필요합니다.",
        });
        return;
    }

    try {
        // 검증 ( userId만 필요)
        const {userId} = jwt.verify(authToken, "시크릿키");
        //console.log("TEST;", userId)
        Users.findByPk(userId).then((user) => {
            res.locals.user = user;
            //console.log(res.locals.user)
// **** 반드시 next 먼저 호출해야함 안그러면 미들웨어 레벨 예외처리 걸려서 그 뒤에있는 미들웨어는 연결 x
            next();
        });
    } catch (err) {
        res.status(401).send({
            errorMessage: "로그인이 필요한 기능입니다.",
        });
    }
};

 

migrations/user.js

더보기
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Users', {
      userId: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      nickname: {
        type: Sequelize.STRING
      },
      password: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Users');
  }
};

migrations/posts.js

더보기
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Posts', {
      postId: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      nickname: {
        type: Sequelize.STRING
      },
      title: {
        type: Sequelize.STRING
      },
      content: {
        type: Sequelize.STRING
      },
      likes: {
        type: Sequelize.INTEGER
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Posts');
  }
};

migrations/like.js

더보기
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Likes', {
      likeId: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      postId: {
        type: Sequelize.INTEGER
      },
      nickname: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Likes');
  }
};

migrations/comment.js

더보기
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Comments', {
      commentId: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      postId: {
        type: Sequelize.INTEGER
      },
      nickname: {
        type: Sequelize.STRING
      },
      comment: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Comments');
  }
};

 

models/user.js

더보기
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Users extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  Users.init({
      userId: {
        primaryKey: true,
        type: DataTypes.INTEGER,
      },
    nickname: DataTypes.STRING,
    password: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'Users',
  });
  return Users;
};

models/posts.js

더보기
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Posts extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  Posts.init({
    postId: {
      primaryKey: true,
      type: DataTypes.INTEGER,
      autoIncrement: true
    },
    nickname: DataTypes.STRING,
    title: DataTypes.STRING,
    content: DataTypes.STRING,
    likes: DataTypes.INTEGER
  }, {
    sequelize,
    modelName: 'Posts',
  });
  return Posts;
};

models/like.js

더보기
'use strict';
const {
    Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
    class Likes extends Model {
        /**
         * Helper method for defining associations.
         * This method is not a part of Sequelize lifecycle.
         * The `models/index` file will call this method automatically.
         */
        static associate(models) {
            // define association here
        }
    }

    Likes.init({
        likeId: {
            primaryKey: true,
            type: DataTypes.INTEGER,
            autoIncrement: true
        },
        postId:  DataTypes.INTEGER,
        nickname: DataTypes.STRING,
    }, {
        sequelize,
        modelName: 'Likes',
    });
    return Likes;
};

models/comment.js

더보기
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Comments extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  Comments.init({  //💛💛
    commentId: {
      primaryKey: true,
      type: DataTypes.INTEGER,
      autoIncrement: true
    },
    postId: DataTypes.INTEGER,
    nickname: DataTypes.STRING,
    comment: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'Comments', //💛💛
  });
  return Comments;  //💛💛
};

 

routes/signup.js

더보기
const express = require("express");
const router = express.Router();
const { Users } = require("../models"); //⭐
const Joi = require("joi");
const userSchema = Joi.object({
    nickname: Joi.string(), //.pattern(new RegExp("^[a-zA-Z0-9]{3,30}$")).required(),
    password: Joi.string(), //.pattern(new RegExp("^{4,30}$")).required(),
    confirm: Joi.string() //.pattern(new RegExp("^{4,30}$")).required(),
});

router.post("/signup", async (req, res) => {
    try {
        const {nickname, password, confirm} = await userSchema.validateAsync(req.body);  //joi...⁉⁉⁉⁉⁉

        if (password.search(nickname) === nickname) {
            res.status(400).send({ errorMessage: "형식에 맞지 않는 비밀번호입니다." });
            return;
        }

        if (password !== confirm) {
            res.status(400).send({ errorMessage: "패스워드가 패스워드 확인란과 다릅니다." });
            return;
        }
        // nickname 동일한게 이미 있는지 확인
        const existsUsers = await Users.findOne({where: { nickname }})
        if (existsUsers) {
            res.status(400).send({ errorMessage: "중복된 닉네임입니다." });
            return;
        }

        await Users.create({nickname, password});
        res.status(201).send({message: "회원가입에 성공하였습니다."});

    } catch (error) {
        console.log(`${req.method} ${req.originalUrl} : ${error.message}`);
        res.status(400).send({ errorMessage: "요청한 데이터 형식이 올바르지 않습니다." });
    }
});

module.exports = router;

routes/login.js

더보기
const express = require("express");
const {Users} = require("../models"); //⭐
const router = express.Router();
const jwt = require("jsonwebtoken");

router.post("/login", async (req, res) => {
    const { nickname, password } = req.body;
    const user = await Users.findOne({where: { nickname }});

    if (!user || password !== user.password || nickname !== user.nickname) {
        res.status(400).send({
            errorMessage: "닉네임 또는 패스워드를 확인해주세요.",
        });
        return;
    }

    res.send({
        token: jwt.sign({ userId: user.userId }, "시크릿키"),
    });
});

module.exports = router;

routes/posts.js

더보기
const express = require('express');
const router = express.Router();
const {Posts} = require("../models"); //⭐
const {Likes} = require("../models");  //⭐
const authMiddleware = require("../middlewares/auth-middleware");


//1. 전체 게시글 목록 조회 API O
router.get("/posts", async (req, res) => {
    const item = await Posts.findAll({order: [["updatedAt", "DESC"]]})
    const mapItem = item.map((item) => {
        return {
            postId: item._id,
            nickname: item.nickname,
            title: item.title,
            createdAt: item.createdAt,
            updatedAt: item.updatedAt,
            likes: item.likes
        }
    })
    res.json({item: mapItem});
});


//6. 라잌 게시글 목록 조회 API O
router.get("/posts/like", authMiddleware, async (req, res) => {
    const { nickname } = res.locals.user;  //💛💛💛💛💛
    const arrLike = await Likes.findAll({ where: { nickname } })  //💛💛💛💛💛
    const arrPostId = arrLike.map((val) => {  //💛💛💛💛💛
        return val.postId  //postId만 리턴해서 배열한 것
    })

    const item = await Posts.findAll({ where: { postId: arrPostId }, order: [[ "likes", "DESC" ]]})   //💛💛💛💛💛
    const mapItem = item.map((item) => {
        return {
            postId: item.postId,
            nickname: item.nickname,
            title: item.title,  //findOne 관계 지어져 있으면 join으로 가져옴.. 여기 title을 그냥 써버리면 게시글 수정되면 반영이 안됨
            createdAt: item.createdAt,
            updatedAt: item.updatedAt,
            likes: item.likes
        }
    })
    res.json({ item: mapItem/*, test: "test" */});
});


//7. 라잌/디스라잌 게시글 API O
router.put("/posts/:postId/like", authMiddleware, async (req, res) => {
        const { postId } = req.params;
        const { nickname } = res.locals.user;
        const is_liked = await Likes.findOne({ where: { postId, nickname } });
        console.log(is_liked)

        /*취소*/
        if (is_liked) {
            await Likes.destroy({ where:  { postId, nickname } } );
            const post = await Posts.findOne({ where: { postId } } );
            likesnum = (post.likes) * 1 - 1;
            await Posts.update({ likes: likesnum }, { where: { postId } });
            res.send({ result: "success", message: "게시글의 좋아요를 취소하였습니다." });
        }

        /*신규*/
        else {
            const post = await Posts.findOne({ where: { postId } });
            likesnum = (post.likes) * 1 + 1;
            await Likes.create({ postId, nickname });
            await Posts.update({ likes: likesnum }, { where: { postId } });
            res.send({result: "success", message: "게시글의 좋아요를 등록하였습니다."});
        }
    }
);


//2. 게시글 상세 조회 API O
router.get("/posts/:postId", async (req, res) => {
    const {postId} = req.params;
    const posts = await Posts.findAll({})
    const filteredPosts = posts.filter((item) => {
        return item["postId"].toString() === postId;
    });
    const mapPosts = filteredPosts.map((item) => {
        return {
            postId: item.postId,
            nickname: item.nickname,
            title: item.title,
            content: item.content,
            createdAt: item.createdAt,
            updatedAt: item.updatedAt,
            likes: item.likes
        }
    })
    res.json({mapPosts});
})


//3. 게시글 작성 API O
router.post("/posts", authMiddleware, async (req, res) => {
    try {
        const {user} = res.locals;
        const existPosts = await Posts.findAll({order: [["postId", "DESC"]]})

        if (existPosts.length !== 0) {
            postId = existPosts[0].postId + 1;
            await Posts.create({
                postId: postId,
                nickname: user.nickname,
                title: req.body.title,
                content: req.body.content,
                likes: 0
            });
            res.status(201).send({message: "게시글 작성에 성공하였습니다."});
        } else {
            await Posts.create({
                postId: 1,
                nickname: user.nickname,
                title: req.body.title,
                content: req.body.content,
                likes: 0
            });
            res.status(201).send({message: "게시글 작성에 성공하였습니다."});
        }
    } catch (Error) {
        return res.status(400).json({message: "게시글 작성에 실패하였습니다."});
    }
})


//4. 게시글 수정 API O
router.put("/posts/:postId", authMiddleware, async (req, res) => {
    const {postId} = req.params;
    const {content, title} = req.body;
    const existsPost = await Posts.findOne({where: {postId}});

    if (existsPost) {
        await Posts.update({ content, title }, { where: { postId } });
        res.send({result: "success", message: "게시글을 수정하였습니다."});
    } else {
        res.send({result: "fail"});
    }
})


//5. 게시글 삭제 API O
router.delete("/posts/:postId", authMiddleware, async (req, res) => {
    const {postId} = req.params;
    const existsPost = await Posts.findOne({ postId });
    if (existsPost) {
        await Posts.destroy({ where: { postId }});
        res.send({result: "success", message: "게시글을 삭제하였습니다."});
    } else {
        res.send({result: "fail"});
    }
})


module.exports = router;

routes/comment.js

더보기
const express = require('express');
const router = express.Router();
const {Comments} = require("../models")
const authMiddleware = require("../middlewares/auth-middleware");


//1. 댓글 목록 조회 API O
router.get("/comments/:postId", async (req, res) => {
    const { postId } = req.params;
    const existPosts = await Comments.findAll({where: {postId}, order: [["updatedAt", "DESC"]]})
    const mapComments = existPosts.map((item) => {
        return {
            postId: item.postId,
            commentId: item.commentId,
            nickname: item.nickname,
            comment: item.comment,
            createdAt: item.createdAt,
            updatedAt: item.updatedAt
        }
    })
    res.json({mapComments});
});


//2. 댓글 작성 O
router.post("/comments/:postId", authMiddleware, async (req, res) => {
    const { postId } = req.params;
    const { user } = res.locals;
        if (req.body.comment === "") {
            res.status(400).json({success: false, errorMessage: "댓글 내용을 입력해주세요"});
        } else {
            await Comments.create({
                postId: postId,
                nickname: user.nickname,
                comment: req.body.comment
            });
            res.status(201).send({message: "댓글 등록이 완료되었습니다."});
        }
})


//3. 댓글 수정 O
router.put("/comments/:commentId", authMiddleware, async (req, res) => {
    try {
        const { commentId } = req.params;
        const { nickname } = res.locals.user;
        const { comment } = req.body;
        const existsComment = await Comments.findOne({where: {commentId, nickname}});
        if (comment === "") {
            res.status(400).json({success: false, errorMessage: "댓글 내용을 입력해주세요"});
        }
        if (existsComment) {
            await Comments.update({ comment }, {where: { commentId, nickname }});
            res.send({message: "댓글을 수정하였습니다."})
        } else {
            res.status(400).json({
                success: false, errorMessage: "본인의 댓글만 수정할 수 있어요!"
            })
        }
    } catch (e) {
        return res.status(400).json({success: false});
    }
})


//4. 댓글 삭제 O
router.delete("/comments/:commentId", authMiddleware, async (req, res) => {
    try {
        const { commentId } = req.params;
        const { nickname } = res.locals.user;
        const existsComment = await Comments.findOne({where: { commentId, nickname }});
        if (existsComment) {
            await Comments.destroy({where: { commentId }});
            res.send({result: "success", message: "댓글을 삭제하였습니다."});
        } else {
            res.status(400).json({success: false, errorMessage: "본인의 댓글만 삭제할 수 있어요!"});
        }
    } catch (e) {
        return res.status(400).json({ result: "fail" });
    }
})


module.exports = router;