express에 use를 차례로 연결시킬 때 순서대로 실행되기 때문에 순서에 유의해야 함. 마지막에 이런 코드를 두면 무조건 에러가 나는 게 아닌가 궁금했었는데 indexRouter 등 앞에 쓰여진 라우터들이 먼저 실행되서 종료되기에 여기까지 오지 않아서 에러가 나지 않음. 특히 아래 err 변수가 하나 더 있는 라우터에는 next(error)를 줘야 저 라우터로 가게 됨.
app.use((req, res, next) => {
const error= new Error(`${req.method} ${req.url} 라우터가 없습니다`);
error.status= 404;
next(err);
});
app.use((err, req, res, next) => {
console.log('aasdf');
res.locals.message= err.message;
res.locals.error= process.env.NODE_ENV !== 'production'? err : {};
res.status(err.status || 500);
res.render('error');
});
db를 정의할 때 1:N 관계에서는 1.hasMany(N) N.belongsTo(1) 로 표기하고 뒤의 인자로 foreignkey를 지정함. hasmany에선 sourcekey, belongsTo에서는 targetkey도 지정해야 함. 따로 지정안하면 알아서 모델명+기본키 (UserId)처럼 만들어서 연결해줌. 1:1 에서는 hasOne과 belongsTo를 사용하는데 이때 belongsTo를 사용하는 모델에 참조키가 생기니 유의해야 함. N:M은 belongsToMany를 쓰고 through로 새로 만들 테이블 이름을 지정해줄 수 있음. 이때 만들어진 테이블은 모델 파일로 만들어 놓지 않으니 꺼내 쓸 때 db.sequelize.models.테이블명 해서 꺼내 쓰면 됨. 실제 테이블 명은 앞자리가 소문자에 s가 붙는 듯.
const env= process.env.NODE_ENV || 'development';
const config = require('../config/config')[env]; //config 폴더에 config.json 파일을 참고하되
// test, production 중 development 부분을 참고함. db 이름, 사용자 등 정보가 적혀 있음
.post(async (req, res, next) => {
try { //axios.post('/users', {name, age, married}); 처럼 주면 req.body 로 사용 가능
console.log('routes/user post호출됨');
const user= await User.create({
name: req.body.name,
age: req.body.age,
married: req.body.married,
}); //생략
router.get('/:id/comments', async (req, res, next) => { //:id 로 변수로 표현가능. 이 변수는 req.params.id로 조회할 수 있음.
html 때 createElement하고 이 요소를 appendChild 해버리면 이 요소는 다시 쓸 수 없음. 다시 만들지 않으면 에러남.
빈 문자열은 false임
req로 이제 요청의 데이터를 확인하면 응답의 res를 주는데 주로 get에서는 res.render로 화면을 띄우고 get이 아니면 res.json으로 데이터를 주는 것 같음.
const morgan= require('morgan'); //로그로 상세하게 출력해줌
app.use(morgan('dev'));
const nunjucks= require('nunjucks');
nunjucks.configure('views', {
express: app, //express an express app that nunjucks should install to
watch: true, //watch (default: false) reload templates when they are changed
});
const dotenv= require('dotenv');
dotenv.config(); //이걸 해야 process.env로 쓸 수 있음 안하면 undefied
const pageRouter= require('./routes/page'); //각각 다 분리해놔야 좋은 코드임^^
const authRouter= require('./routes/auth');
const postRouter= require('./routes/post');
const userRouter= require('./routes/user');
const { sequelize }= require('./models'); // 이렇게 따로 지정하는 게 없으면 index.js를 알아서 지정함
const passport= require('passport');
const passportConfig= require('./passport')
passportConfig();
const app= express();
app.set('port', process.env.PORT || 8001); //이렇게 app.set으로 저장해놓으면 app.get('port')로 찾을 수 있음
app.set('view engine', 'html');
sequelize.sync({force:false}).then(()=>{console.log('연결 성공');}).catch((err)=>{console.error(err);});
//sync({ force: true }) - This creates the table, dropping it first if it already existed
app.use(express.static(path.join(__dirname, 'public'))); //public 폴더 안의 파일을 전역 파일처럼 localhost:3000/파일이름 해서 접근 가능
app.use('/img', express.static(path.join(__dirname, 'uploads'))); //localhost:3000/img/파일이름
location.href=> href 는 location 객체에 속해있는 프로퍼티로 현재 접속중인 페이지 정보를 갖고 있음. 또한 값을 변경할 수 있는 프로퍼티이기 때문에 다른 페이지로 이동하는데도 사용됨. 새로고침 잘 안 될 때 location.reload(true) 주면 잘 됨
document.getElementById('post-image').addEventListener('change', function(e) {
const formData= new FormData(); //Don't forget the enctype="multipart/form-data" in your form.
console.log(this, this.files);
formData.append('img', this.files[0]); //upload.single('img'), It is important that you use the name field value from the form in your upload function.
axios.post('/post/img', formData)
.then((res) => {
document.getElementById('post-image-preview').src= res.data.url; //post하면 res.json({url:'--'})로 데이터 넣어주니 그걸 갖다 씀
document.getElementById('post-image-url').value= res.data.url;
document.getElementById('post-image-preview').style.display= 'inline';
}).catch((err)=>{ console.error(err); });
});
router.post('/img', isLogin, upload.single('img'), (req, res)=>{
console.log('/img 잘 도착하고 됨', req.file);
//. The body object contains the values of the text fields of the form, the file or files object contains the files uploaded via the form.
res.json({url: `/img/${req.file.filename}`});
//document.getElementById('post-image-preview').src= res.data.url; 에서 씀
//static으로 img로 하면 uploads 폴더로 연결해줌
});
const ext= path.extname(file.originalname); //확장자만 줌 asdf.html이면 '.html'
cb(null, path.basename(file.originalname, ext)+Date.now()+ext);
//originalname: Name of the file on the user’s computer
//path.basename(path, extension) -> 주어진 path에서 file name만 리턴해 줌.
//단 extension이 주어지고 file name이 extension으로 끝난다면 이를 제외하고 리턴해 줌
//즉 'C:\user\folder1\asdf.html', '.html'이 주어지면 -> asdf
//여기에 현재 날짜를 붙여 중복되지 않도록 만들어주고 다시 확장자를 붙여줌
//https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_Expressions
//https://heropy.blog/2018/10/28/regexp/
//g 모든 문자열에서 검사, [^ ] <- 이 아닌 문자열, \s는 공백 의미, +는 1회 이상 반복 가능
const hashtags= req.body.post_text.match(/#[^\s#]+/g);
{% if post.dataValues.good %}
<h3 id="post-good" style="cursor:pointer">♥</h3>
{% else %}
<h3 id="post-good" style="cursor:pointer">♡</h3>
await Promise.all(
posts.map(async(list,idx)=>{
const result= await UserPost.findOne({
where:{
UserId:req.user.id,
PostId:list.id,
}
}); //idx는 index, datavalue는 디비에 컬럼이 아닌 다른 속성을 넣고 싶을 때 쓰면 됨
if(result) posts[idx]['dataValues'].good=true;
else posts[idx]['dataValues'].good=false;
})
);
//http://www.passportjs.org/packages/passport-local/ 참고
module.exports= () => { //참고할 폼 네임 필드
passport.use(new LocalStrategy({usernameField: 'email', passwordField: 'password'}, async (email, password, done) => {
try {
const exUser= await User.findOne({ where: {email: email}});
//라우터에서 passport.authenticate('local', (authError, user, info)때 사용, ${info.message}
if(exUser) {
const result= await bcrypt.compare(password, exUser.password);
if(result) {
done(null, exUser);
} else {
done(null, false, {message: '비밀번호가 일치하지 않습니다.'});
} //뒤에 인자들을 생략 가능, undefined로 전달됨 다만 앞에껄 생략하고 뒤에꺼만 줄 수는 없음
모든 요청 전에 passport serializeUser, deserializeUser 가 실행됨. 앞에 이런 식으로 res.locals.user= req.user; 넣어두면 html 파일에서 변수로 쓸 수 있음.
댓글