2017년 6월 13일 화요일

HTML 작명법 : Snake case, Camel case

HTML 혹은 변수명을 작성할때 두단어 이상의 합성어 명칭 작성법
이런 이름으로 부르고 있는 줄 몰랐네요.
이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2017년 4월 7일 금요일

External monitor is not working after Mac Sierra 10.12.4 was upgraded

External monitor is not working after Mac Sierra 10.12.4 was upgraded

Environment

  • MacBook Pro : Mac Sierra OS X 10.12.4 upgrade
  • External Monitor : Crossover 285K UHD LED(4K)
  • connected using HDMI-HDMI cable between Mac and Monitor


Problem
After Mac Sierra 10.12.4 upgrade, external monitor is not detected and is not working.
Sometimes external monitor is working as input source change in monitor.

Solution
Change cable from HDMI-HDMI to Thunderbolt-DisplayPort.

Now my external monitor is working well.



2016년 2월 18일 목요일

워드프레스] 기 등록된 Media 정보 file url 정보 변경 방법


워드프레스에 기 등록된 Media의 File URL 정보를 MySQL DB에서 변경



  1. MySQL DB에 접속한다.
  2. wp_posts 테이블에서 attachment_id와 일치하는 게시물의 정보를 조회후 변경하는 Query를 다음과 같이 실행한다.

mysql> select id, guid from newface_posts where id = 906;
+-----+--------------------------------------------------------------------+
| id  | guid                                                               |
+-----+--------------------------------------------------------------------+
| 906 | http://old_ip/wp-content/uploads/2015/06/치아미백024.jpg     |
+-----+--------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> update newface_posts set guid = replace(guid, 'http://old_ip', 'http://new_ip') where id = 906;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select id, guid from newface_posts where id = 906;                       +-----+-------------------------------------------------------------------+
| id  | guid                                                              |
+-----+-------------------------------------------------------------------+
| 906 | http://new_ip/wp-content/uploads/2015/06/치아미백024.jpg     |
+-----+-------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>

워드프레스에서 다시 게시물을 조회하면 변경되어 보여진다.
이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 29일 수요일

Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.

발생현상]

Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.

node.js에서 Mocha를 이용해서 테스트 실행중 특정 시점이후 발생하는 오류

node.js에서 MySql에서 사용자 정보 조회, 등록을 5번의 테스트 case로 수행함.
이때 DB Connection Pool의 connectionLimit은 "3"으로 설정되어 있음. 

test case : 회원가입
    describe("회원가입 ->", function() {
        it ("회원가입 실패(필수값 누락)", function(done) {
            var data = {
                user_id: user_id,
                password : password,
                user_name : '김치손',
                email : '',
                phone : '',
                cellphone : '',
                sex : '',
                logintype : mem_type
            };
            request(svr)
                .post("/member/register")
                .send(data)
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    expect('ER_BAD_NULL_ERROR').to.equal(res.body.code);
                    done();
                });
        });


routes.js : 
// 회원 정보를 저장
router.post('/register', function(req, res, next) {
    console.log(req.body);

    var loginType = req.body.logintype;
    var vUserId = req.body.user_id;

    var datas = [
        req.body.user_id,
        req.body.user_name,
        req.body.password,
        req.body.nickname,
        req.body.email,
        req.body.sex,
        req.body.phone,
        req.body.address,
        req.body.logintype
    ];
    // TODO 이메일 중복, 체크 로직 추가

        memberProvider.insertMember(datas, function(err, sInsertId) {
            if (err) {
                res.send(CONSTS.getErrData(err.code));
                return;
            }
            res.send(CONSTS.getErrData('0000'));
        });
});

db.js
var mysql = require('mysql');
var pool = mysql.createPool({
    connectionLimit: 3,
    user: 'xxxx',
    password: 'xxxx',
    database: 'xxxx'
});

var sqlMember = {
     //신규 회원 가입
    "insertNew": "insert into tb_member(user_id, user_name, " +
        "user_password, user_nickname, email, sex, phone, address, mem_type, reg_date) " +
        "values(?, ?, ?, ?, ?, ?, ?, ?, ?, now())",
    // 회원 정보 갱신
    "upateInfo": "update tb_member set user_password = ?, edit_date = now() " +
        "where id = ? ",
    // 회원 정보 조회
    "selectInfo": "select * from tb_member where user_id = ? and mem_type = ?",
    // 회원 정보 삭제
    "deleteMember": "delete from tb_member where user_id = ? and mem_type = ?",
};

MemberProvider = function() {

}

MemberProvider.prototype.insertMember = function (datas, callback) {
    // console.log('insertMember : ' + datas);
    // TODO Password 저장시 암호화 필요함.
    //      암호화 후 로그인에서 비밀번호 체크 로직 보완 필요함.
    pool.getConnection(function (err, conn) {
        conn.query(sqlMember.insertNew, datas, function(err, rows) {
            if (err) {
                conn.release;
                console.error("err : " + err);
                callback(err);
            } else {
                console.log("rows : " + JSON.stringify(rows));
                conn.release;
                callback(null, rows.insertId);
            }
        });
    });
};

수행결과
...
data : mocha02,C
  1) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 성공
    치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 실패: { user_id: 'mocha02', password: 'a', mem_type: 'C' }
queryID : selectInfo
data : mocha02,C
  2) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 실패
  3) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 치과병원정보 삭제
  3 passing (15s)
  3 failing

  1) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 성공:
     Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.
 

  2) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 실패:
     Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.
 

  3) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 치과병원정보 삭제:
     Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.

발생원인]

Mocha test case 5번 모두가 비동기로 수행된다.
비동기로 수행된다는 것은 모두 동시에 DB에 접속해서 작업을 수행하게 된다.
이때, 문제는 동시에 5개의 test case 모두 db에 접속하려고 하므로 connectionLimit : 3의 제약에 따라서 대기하게 되므로 Timeout 오류가 발생하는 것이다.
수행결과 화면에 뿌려지는 결과는 수행결과를 그냥 순차적으로 표시하는 것으로 보인다.

이제까지 내가 착각한 부분 Mocha가 순차적으로 하나씩 수행하므로 DB connection Pool 의 connectionLimit에는 영향이 없다고 판단한 착오가 있었다.

해결방법]

동시 수행하는 test case 만큼 db connection pool의 설정에서 limit 개수를 올려준다.
var mysql = require('mysql');
var pool = mysql.createPool({
    connectionLimit: 10,
    user: 'xxxx',
    password: 'xxxx',
    database: 'xxxx'
});


이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 22일 수요일

Node.js + Multer + Ajax를 사용한 파일 업로드 예제

Node.js에서 Multer를 이용하여 파일 업로드를 수행하는 예제
Multer는 multipart/form-data 처리를 위한 미들웨어임
1. app.js 에 multer 선언
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var multer  = require('multer');

var office = require('./routes/office');

var app = express();

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(multer());
app.use(cookieParser());
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
}));
app.use(express.static(path.join(__dirname, 'public')));


2. office.js Route 파일
- file upload를 실행 로직
var CONSTS = require('./consts');
var express = require('express');
var fs = require('fs');

router.post('/info/upload-new-photo', function(req, res, next) {
    console.log(req.body);
    console.log(req.files);
    var imageFile = req.files.uploadnewphoto;
    if (imageFile) {
        // 변수 선언
        var name = imageFile.name;
        var path = imageFile.path;
        var type = imageFile.mimetype;
        // 이미지 파일 확인
        if (type.indexOf('image') != -1) {
            // 이미지 파일의 경우 : 파일 이름을 변경합니다.
            var outputPath = CONSTS.UPLOADPATH + Date.now() + '_' + name;
            fs.rename(path, outputPath, function (err) {
                if (err) {
                    res.send(CONSTS.getErrData(err.code));
                    return;
                }
                res.send(CONSTS.getErrData('0000'));

            });
        } else {
            // 이미지 파일이 아닌 경우 : 파일 이름 제거
            fs.unlink(path, function(err) {
                res.send(CONSTS.getErrData('E004'));
            });
        }
    } else {
        res.send(CONSTS.getErrData('E003'));
    }
});

3. basic.jade 파일
- 파일 업로드용 client 파일
- Ajax로 파일을 전송한다.
    script(type="text/javascript").
        $(document).ready(function() {
            $("#photo-update-action-selector").click(function() {
                $("#photo-update-action-selector").children(".select-items").toggle();
            });

            $("#upload-new-photo").click(function() {
                $("#photo-upload-error-box").html("");
                $("#uploadnewphoto").click();
            });

            $("#remove-photo").click(function() {

            });

            $("#uploadnewphoto").change(function() {
                var file = this.files[0];

                if (checkUploadFile(file, 700) == true) {
                    var formData = new FormData($('#frmOffice')[0]);
                    //- formData.append('file', $("#uploadnewphoto")[0].files[0]);

                    $.ajax({
                        url: "/office/info/upload-new-photo",
                        type: "POST",
                        processData: false,
                        contentType: false,
                        data: formData,
                        success: function(result) {
                            if (result.code == "0000") {
                                alert('success');
                                //- location.href = "/home#{logintype}";
                            } else {
                                $("#photo-upload-error-box").html(result.message);
                            }
                        },
                        error: function(req, status, err) {
                            //- alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error);
                            $("#errormsg").html("code:"+status+"\n"+"message:"+req.responseText+"\n"+"error:"+err);
                        }
                    });
                }
            });
        });

        form#frmOffice(name="frmOffice", enctype="multipart/form-data")
            div.row-item
                fieldset
                    legend.row-title  로고
                    div.settings-item
                        div#photo
                            div
                                div
                                    div.photo-preview.left
                                        img#photo-upload-preview(src="/images/no-profile-pic.png")
                                    div.upload-action-selector.left
                                        div#photo-update-action-selector.select-container
                                            div.select-arrow
                                            div.selected-item 이미지 변경
                                            ul.select-items(style="display:none")
                                                li#upload-new-photo.select-enabled.select-hovering-item 새 이미지 업로드
                                                li#remove-photo.select-enabled 제거
                                        div#photo-upload-error-box.error-message
                                        div.accepted-photo-types .jpg, .gif, .png. 최대 파일 크기는 700K입니다.
                                        div
                                            input#uploadnewphoto(type="file" name="uploadnewphoto")


이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

Node.js + Mocha 테스트] 로그인 후(세션 생성) 파일 Upload 수행 테스트

Mocha 이용한 테스트로써 먼저 로그인을 수행해서 사용자 세션을 만든 후 파일 업로드를 수행하는 예제

1. testOfficeFile.js
- cookie를 이용하여 로그인시 생성되는 세션정보 설정
- 파일 업로드시 set('cookie', cookie)로 세션정보 set
- attach로 첨부파일 전송
var should = require('should');
var assert = require("assert")
var request = require("supertest");
var expect = require("chai").expect;
var server = require("../app.js");

describe("테스트 ->", function() {
    var svr = "http://localhost:4000";
    var user_id = 'bbb';
    var password = 'bbb';
    var password_wrong = 'a';
    var mem_type = 'C';
    var cookie;

    before(function() {
        server.listen(4000);
    });

    describe("테스트 ->", function() {
        // 로그인 실행
        it('login', loginOffice());
        // 이미지 upload
        it ("이미지 파일 Upload", function(done) {
            var data = {
                user_id: user_id,
                password : password,
                user_name : '김치과',
                email : '',
                phone : '',
                cellphone : '',
                sex : '',
                logintype : mem_type
            };
            request(svr)
                .post("/office/info/upload-new-photo")
                .set('cookie', cookie)
                .field('Content-Type', 'multipart/form-data')
                .send(data)
                .attach('uploadnewphoto', '/Users/baesunghan/Downloads/testlogo.jpg')
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    console.log(res.body.code);
                    console.log(res.body.message);
                    expect('0000').to.equal(res.body.code);
                    done();
                });
        });
    });

    function loginOffice() {
        return function(done) {
            var data = {
                user_id: user_id,
                password : password,
                mem_type : mem_type
            };
            request(svr)
                .post("/member/login")
                .send(data)
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    res.body.code.should.be.equal('0000');
                    // 로그인 성공후 세션 정보를 cookie에 설정
                    cookie = res.headers['set-cookie'];
                    done();
                });
        }
    }
});


2. office.js Route 파일
- file upload를 실행 로직
var CONSTS = require('./consts');
var express = require('express');
var fs = require('fs');

router.post('/info/upload-new-photo', function(req, res, next) {
    console.log(req.body);
    console.log(req.files);
    var imageFile = req.files.uploadnewphoto;
    if (imageFile) {
        // 변수 선언
        var name = imageFile.name;
        var path = imageFile.path;
        var type = imageFile.mimetype;
        // 이미지 파일 확인
        if (type.indexOf('image') != -1) {
            // 이미지 파일의 경우 : 파일 이름을 변경합니다.
            var outputPath = CONSTS.UPLOADPATH + Date.now() + '_' + name;
            fs.rename(path, outputPath, function (err) {
                if (err) {
                    res.send(CONSTS.getErrData(err.code));
                    return;
                }
                res.send(CONSTS.getErrData('0000'));

            });
        } else {
            // 이미지 파일이 아닌 경우 : 파일 이름 제거
            fs.unlink(path, function(err) {
                res.send(CONSTS.getErrData('E004'));
            });
        }
    } else {
        res.send(CONSTS.getErrData('E003'));
    }
});

3. 실행
baesunghan:~/git/dentaljobs$mocha ./test/testOfficeFile

    테스트 -> 테스트 -> login: { user_id: 'bbb', password: 'bbb', mem_type: 'C' }
{ code: '0000', message: 'SUCCESS!!!' }
POST /member/login 200 44.848 ms - 38
  ․ 테스트 -> 테스트 -> login: 66ms
    테스트 -> 테스트 -> 이미지 파일 Upload: session이 있음.
start /office/info/upload-new-photo
{ 'Content-Type': 'multipart/form-data' }
{ uploadnewphoto:
   { fieldname: 'uploadnewphoto',
     originalname: 'testlogo.jpg',
     name: 'e3bb6d90e617db3717bba6174aaddaaa.jpg',
     encoding: '7bit',
     mimetype: 'image/jpeg',
     path: '/var/folders/0y/124f1qys05q4gvfgs1f4bg2h0000gn/T/e3bb6d90e617db3717bba6174aaddaaa.jpg',
     extension: 'jpg',
     size: 2208,
     truncated: false,
     buffer: null } }
POST /office/info/upload-new-photo
200 17.854 ms - 38
0000
SUCCESS!!!
  ․ 테스트 -> 테스트 -> 이미지 파일 Upload: 32ms

  2 passing (111ms)
baesunghan:~/git/dentaljobs$

이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 17일 금요일

민주주의의 재발견(박상훈)을 읽고서


민주주의는 정당정치이고 이 정당은 당원들의 의견에 부합해서 정치를 해야한다고 한다. 
내가 이전까지 생각하던 모든 국민을 위한 정치를 해야 올바른 정당이라고 생각했었는데
그런 일은 있을수 없고 또한 그건 정당정치로써의 민주주의가 아니다라고 말한다. 

이를 적용해보면 현 상황에 적용해자.
새누리당은 보수로서 좀 있는, 많이 가진사람들을 대변하고 있고 그들의 의견을 대변해서 관철시키고 있으므로 잘 하고 있는 것이고, 새정치민주연합은 좀 덜 가지고 있는 사람들의 의견을 대변해서 제대로 관철시키지 못하므로 잘 하고 있지 못하는 것이다.
또한 마찬가지로 진보정의당, 민노당도 마찬가지...

그러나 여기서 정치가 공평하지 않는 상황에서 치러지고 있는 현실을 감안하지 않고 있다. 
대부분의 사람들이 접하는 매체, 즉 방송, 신문등이 새누리, 정부의 나팔수인 상황에서 대다수의 사람들은 새누리의 여러가지를 정책을 오인하게 만든다.
분명히 많을 논의가 필요한 많은 상황에서도, 다수를 통한 일방적인 밀어붙이기식을 해도 지지층에서는 별다른 문제제기가 없는 듯 하다.
이건 아니라고 본다.

가장 아쉬운 점은 어떤 이유에서인지 민주주의 정당의 한 축인 새누리나 다른 소수 정당에 대한 작가의 의견이나 평가가 전혀 없다.
작가는 새누리나 정의당, 민노당에 대해서는 어떻게 판단하는지도 같이 적었다면 더 좋았을듯 한데 아쉽다.

이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.