개발 일정
일자 | to do |
23.11.14(화) | API 만들기 |
23.11.15(수) | - 공통 응답 모듈 만들기 - 파일 업/다운로드 로그인한 사용자 검사 추가 - 세션관리 모듈 만들기(클라이언트가 요청하는 모든 API(로그인 제외)헤더에 토큰 있는지 검사하는 모듈 만들기) |
23.11.16(목) | - 로그관리: 유저가 어떤 API를 언제 호출했는지 / - 코드 보완: 예외처리, 중복코드 수정 |
23.11.17(금) | - 이메일 중복검사 같은 세세한 부분 체크 - 구글 클라우드 플랫폼 VM 서버 올리기(포트 새로 열기) - 서버올리고 포스트맨 테스트 |
API
기능 | 세부기능 | method | url |
계정 추가 | 항목: 이메일, 비밀번호, 이름, 전화번호 | post | /api/users |
계정 삭제 | 계정 삭제 | delete | /api/users |
비밀번호 변경 | 비밀번호 변경 | put | /api/users |
계정인증 | 이메일, 비밀번호를 입력 / 세션 토큰 리턴 | post | /api/account |
토큰인증 | 이메일, 토큰으로 유효성 확인 및 만료시간 갱신 | post | /api/token |
로그인 | 계정 인증, 클라이언트에 필요한 정보 리턴 | post | /api/users/login |
파일 업로드 | 파일 업로드 / 로그인한 사용자만 | post | /api/files |
파일 다운로드 | 파일 다운로드 / 로그인한 사용자만 | get | /api/files |
익스프레스로 노드 프로젝트 생성
필요한 모듈 설치하기
realm
데이터를 저장하기 위해 realm 사용
multer
파일업다운로드를 위해 필요한 모듈
uuid
토큰을 랜덤값으로 주기 위해서 uuid 사용
코드 작성
1) 데이터베이스 realm 을 사용하여 UserSchema 스키마 정의
const Realm = require("realm");
// User 스키마(모델) 정의
let UserSchema = {
name: "User",
properties: {
email: "string",
password: "string",
name: "string",
tel: "string",
token: "string?", // 속성을 선택사항으로 변경하려면 '?'를 붙임
date: "date",
tokenExp: "date",
},
primaryKey: "email",
};
// Realm 객체 생성
let UserRealm = new Realm({
path: "user.realm",
schema: [UserSchema],
// schemaVersion: 1,
});
// Realm 객체 내보내기
module.exports = UserRealm;
2) user CRUD 만들기
Create
var express = require("express");
var router = express.Router();
const uuid = require("uuid"); // uuid 모듈 추가
var UserRealm = require("../db/realm");
/* POST 계정 추가. */
// todo: 이메일 중복체크
router.post("/", function (req, res, next) {
console.log(req.body);
console.log(req.body.email);
var email = req.body.email;
var password = req.body.password;
var name = req.body.name;
var tel = req.body.tel;
console.log(`계정 데이터: ${email}`);
try {
UserRealm.write(() => {
UserRealm.create("User", {
email: email,
password: password,
name: name,
tel: tel,
toeken: "default",
date: new Date(),
tokenExp: new Date(),
});
});
res.status(201).send({
success: true,
message: "계정 추가 성공",
});
} catch (error) {
console.log(error);
res.status(500).send({
success: false,
message: "계정 추가 실패",
});
}
});
Read
/* GET 계정 전체 조회 */
router.get("/", function (req, res, next) {
const users = UserRealm.objects("User").sorted("date", true);
res.send({
success: true,
data: users,
});
});
Update
/* PUT 비밀번호 변경. */
router.put("/:email", function (req, res, next) {
const user = findUserByEmail(req.params.email);
const password = findPassword(user.password, req.body.inputPassword);
try {
if (user && password) {
UserRealm.write(() => {
user.password = req.body.newPassword;
});
res.status(200).send({
success: true,
message: "비밀번호 변경 성공",
});
}
} catch (error) {
console.log(error);
res.status(500).send({
success: false,
message: "비밀번호 변경 실패",
});
}
});
//------------------------------ 공통 메서드------------------------------//
// 이메일 기반 계정 조회
function findUserByEmail(email) {
return UserRealm.objects("User").filtered(`email = "${email}"`)[0];
}
// 이메일 기반 비밀번호 조회
function findPassword(password, inputPassword) {
return password === inputPassword;
}
- 기존 비밀번호랑 같을 새로운 비밀번호 입력하라는 메시지 줄지 고민해보기
Delete
/* DELETE 계정 삭제 */
router.delete("/:email", function (req, res, next) {
const user = findUserByEmail(req.params.email);
try {
if (user) {
UserRealm.write(() => {
UserRealm.delete(user);
});
res.send({
success: true,
message: "계정 삭제 성공",
});
}
} catch (error) {
console.log(error);
res.status(500).send({
success: false,
message: "계정 삭제 실패",
});
}
});
로그인
/* POST 로그인 */
router.post("/login", function (req, res, next) {
const user = findUserByEmail(req.body.email);
console.log(JSON.stringify(user));
const password = findPassword(user.password, req.body.inputPassword);
console.log(`user확인: ${user}`);
console.log(`password확인: ${password}`);
try {
if (user && password) {
UserRealm.write(() => {
const sessionToken = uuid.v4(); // uuid를 사용해서 세션 토큰 생성
user.token = sessionToken; // 세션 토큰 저장
});
res.status(200).send({
success: true,
message: "로그인 성공",
data: user,
});
}
} catch (error) {
console.log(error);
res.status(500).send({
success: false,
message: "로그인 실패",
});
}
});
토큰 인증api
var express = require("express");
var router = express.Router();
var UserRealm = require("../db/realm");
/* GET 토큰 인증 */
router.get("/", function (req, res, next) {
// 헤더에서 이메일과 토큰 추출
const email = req.headers.email;
const token = req.headers.token;
if (!email || !token) {
return res.status(401).send({ message: "이메일과 토큰을 넣어주세요." });
}
try {
const user = UserRealm.objects("User").filtered(
`email = "${email}" AND token = "${token}"`
);
if (user.length === 1) {
// 사용자가 존재하고 토큰이 일치하면 인증 성공
// 토큰 만료시간 갱신
UserRealm.write(() => {
user[0].tokenExp = new Date();
});
return res.status(200).send({
success: true,
message: "토큰 인증 성공",
});
} else {
// 사용자가 없거나 토큰이 일치하지 않으면 인증 실패
return res.status(401).send({
// 401: Unauthorized (인증 실패)
success: false,
message: "토큰 인증 실패",
});
}
} catch (error) {
console.log(error);
res.status(403).send({
// 403: Forbidden (권한 없음)
success: false,
message: "토큰 인증 실패",
});
}
});
module.exports = router;
파일 업로드
const express = require("express");
const router = express.Router();
const multer = require("multer"); // multer 모듈 추가
const fs = require("fs"); // fs 모듈 추가
const path = require("path"); // path 모듈 추가
// 파일을 업로드할 uploads 폴더 생성
fs.readdir("uploads", (error) => {
if (error) {
console.log("uploads 폴더를 생성합니다.");
fs.mkdirSync("uploads");
}
});
// 파일 업로드 설정
const upload = multer({
storage: multer.diskStorage({
// 파일 저장 경로 설정
destination(req, file, cb) {
// cb 콜백 함수를 통해 전송된 파일 저장 디렉토리 설정, 'uploads/' 디렉토리로 지정
cb(null, "uploads/");
},
// 파일 저장명 설정
filename(req, file, cb) {
const ext = path.extname(file.originalname);
// cb(null, path.basename(file.originalname, ext) + new Date().valueOf() + ext);
// cb 콜백 함수를 통해 전송된 파일 이름 설정
// cb(null, path.basename(file.originalname, ext));
cb(null, file.originalname);
console.log(file.originalname);
},
}),
// 파일 최대 용량
limits: { fileSize: 5 * 1024 * 1024 },
});
// api 요청 섹션 시작
/* GET 파일 업로드 */
// 단일 파일 업로드 multer.single(fileName)
router.post("/upload", upload.single("file"), function (req, res, next) {
console.log("단일 파일 업로드 요청");
console.log(req.file);
const imagePath = req.file.path;
if (imagePath === undefined) {
// return res.status(400).send(response.fail(400, "파일이 없습니다."));
return res
.status(400)
.send({ success: false, message: "파일이 없습니다." });
}
res
.status(200)
// .send(response.success(200, "파일 업로드 성공", `저장경로: /${imagePath}`));
.send({
success: true,
message: "파일 업로드 성공",
path: `/${imagePath}`,
});
});
// n개 파일 업로드 multer.array(, 개수제한)
/* POST users listing. */
router.post("/", upload.array("files", 10), function (req, res, next) {
console.log(req.files);
// res.json(`~~post 요청 응답~~`);
res.status(200).send(
// response.success(200, "파일 업로드 성공", `파일개수: ${req.files.length}`)
{ success: true, message: "파일 업로드 성공", count: req.files.length }
);
});
/* GET 파일 다운로드 */
router.get("/upload", function (req, res, next) {
res.send("GET 파일 다운로드");
});
module.exports = router;
파일 다운로드
var express = require("express");
const multer = require("multer");
const path = require("path");
const fs = require("fs");
const createHttpError = require("http-errors");
// const response = require("../util/response");
var router = express.Router(); // router 객체는 express.Router()로 만듦
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads/");
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
/* GET users listing. */
router.get("/download/:fileName", function (req, res, next) {
const fileName = req.params.fileName;
// const filePath = path.join(__dirname, "uploads", fileName);
const filePath = `uploads/${fileName}`;
console.log(`fileName: ${fileName}`);
console.log(`filePath: ${filePath}`);
const ch = fs.existsSync(filePath);
console.log(`파일 존재여부: ${ch}`);
// 다운로드 요청한 파일이 존재하는지 확인
if (fs.existsSync(filePath)) {
// 파일의 MIME 유형 설정(이미지, pdf, txt 등)
res.setHeader("Content-Type", getMimeType(fileName));
// 파일의 Content-Disposition 헤더를 "inline"으로 설정하여 파일을 브라우저에서 바로 보기
res.setHeader("Content-Disposition", "inline; filename=" + fileName);
// res.status(200).send(response.success(200), "파일 다운로드 성공");
// 파일 스트림을 응답에 연결
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
} else {
// res.status(404).send(response.fail(404, "파일이 존재하지 않습니다."));
res
.status(404)
.send({ success: false, message: "파일이 존재하지 않습니다." });
}
});
function getMimeType(fileName) {
const extname = path.extname(fileName);
switch (extname.toLowerCase()) {
case ".png":
return "image/png";
case ".jpg":
case ".jpeg":
return "image/jpeg";
case ".pdf":
return "application/pdf";
case ".txt":
return "text/plain";
default:
return "application/octet-stream";
}
}
module.exports = router; // 라운터를 모듈로 만듦
파일 업다운로드는 지난번에 만든 업다운로드 모듈 코드를 사용함
업/다운로드 실습때 공통응답 형식 모듈을 만들었었는데 다른 코드에도 적용해서 수정할 예정
트러블 슈팅
AssertionError: Cannot modify managed objects outside of a write transaction 에러는 Realm에서 관리되는 객체를 트랜잭션 외부에서 수정하려고 할 때 발생합니다. Realm에서는 데이터 변경이 트랜잭션 내에서 이루어져야 하며, write 메서드 내에서 수행되어야 합니다.
'Node.js' 카테고리의 다른 글
[Node.js] exress 프로젝트 로그 관리 방법 (0) | 2023.11.15 |
---|---|
[Node.js] 계정관리 서버 프로젝트 2 (0) | 2023.11.15 |
[Node.js] CommonJS 모듈 (0) | 2023.11.13 |
[Node.js] 모듈 (0) | 2023.11.13 |
[Node.js] 노드에서 파이썬 파일 구동하기 (0) | 2023.11.10 |