꼬마선원의 개발일지 3주차

By ccoma | August 28, 2018


이번 주 했던 삽질들을 적어본다

이번주에는 본격적으로 사용자가 학습하는 콘텐츠들을 보여줄 화면을 만들었다.

홈페이지의 기능은 지난주에 이미 다 완성했다고 생각했는데, 알고보니 안된 기능이 너무 많아서 이거 수정하는데도 오래 걸린 것 같다.

자려고 누우면 생각나고, 자고 일어나면 생각나고, 테스트 하다가 발견하고…

고통의 끝은 어디인가 생각하며 끄적이는 개발일지 3주차 이다.




개발하며 끄적이는 의식의 흐름

이번주에 가장 고통 받았던 부분…
사실 너무 많아서 생각이 정리가 안된다 ㅎㅎㅎ
개발 외에도 이것저것 신경 쓸 일이 너무 많았어서 복잡복잡하기도 했다.

뭐 어쨌든…

지난주 회의 후에 나에게 부여된 역할은 실제 사용자가 있다는 전제하에 사용자, 문제 등의 정보를 데이터베이스에 넣어보기 였다.

그런데 와…

이거 하나 가지고 이렇게 많은 일이 생길줄은 생각도 못했다.

데이터 몇개(는 사실 몇백개 이지만) 넣는거야 파이썬으로 대충 쿼리 만드는 스크립트 짜서 넣으면 금방이지! 라고 생각했었는데, 크나큰 오산이었다.


내가 간과한 부분이 몇 가지 있었는데,

  • 첫째, 사용자의 password는 bcrypt라는 함수를 이용 해 Hash 값을 생성 해 저장해야 한다
  • 둘째, 모든 코스와 문제의 이름은 한글이며, 짧지도 않고, 설명도 포함되어야 하며 공통된 부분이 하나도 없다

이정도가 될 것이다.


지난편 어딘가에 말했듯, 나는 Laravel에서 기본적으로 제공 해 주는 로그인 매커니즘인 attempt()를 사용 해 사용자를 로그인하도록 구현했다. 그런데 이 때, 이 attempt()를 사용하려면 bcrypt라는 함수를 통해 사용자의 패스워드를 Hashing 해 데이터베이스에 저장해야 한다. 그러면 attempt() 함수 호출 시, password 자리에 있는 값을 attempt() 함수 내에서 bcrypt()로 Hashing 해 데이터베이스에 저장되어 있는 값과 비교한다.

내가 파이썬으로 스크립트 짜야지! 하고 생각한 방법은 이 전에 다른 일을 할 때 약 360명의 계정을 생성해야 하는 일이 있었을 때 사용했던 것이다.

대충 이런 방식이다.


1. 저장할 데이터가 있는 파일 만들기
2. 파이썬 스크립트를 구현 해 파일 읽어 와 삽입 쿼리 생성
3. 해당 쿼리 복사해서 MySQL에서 쿼리 실행


이 파이썬 스크립트는 대충 아래와 같이 생겼다.


f = open("./student.txt", "r")
query = "INSERT INTO `team` (`idx, `name`, `email`, `password`, `reg_date`, `school`) VALUES "
for i in range(1, 113):
	line = f.readline()
	data = line.split('\t')
	query += "("+str(i+6) + ", '"+data[1] + "', '"+data[3]+"', sha1('"+data[2]+"'), now(), '"+data[0]+"'),"
print query


이 스크립트를 사용하려면 일단 파일에서 읽어 올 자료가 별로 없거나 파일을 만드는데 오랜 시간이 걸리지 않아야 한다. 시간 단축하자고 만든 스크립트인데, 파일 만드는데 너무 오래걸리면 별로 이점이 없다.

이 때는 데이터베이스에 등록할 명단이 싹 정리되어 있는 파일이 있었기 때문에 스크립트를 짜서 사용하는데 어려움이 없었다.

그런데 이번에는 아예 파일이 없어서 새로 만들어야 한다.


그리고 파이썬에서 bcrypt가 있긴 한데, 이게 통 같은건지 뭔지 구분도 안가고 어떻게 사용하는지 찾는것도 귀찮았다.

거기다 코스랑 문제도 저장해야 하는데, 코스 이름은 쇼핑몰 결제대금 조작하기! 이런식으로 생겼고, 단계는 각 코스 내에 적게는 6개, 많개는 11개의 문제가 들어가는데 적을 알고 나를 알자! 같은 문제 명으로 구성되어 있다.


결국 이걸 데이터베이스에 넣기 위해 쿼리를 짜주는 파이썬 스크립트를 짜더라도, 파일을 생성해서 그 파일에 데이터베이스에 넣을 정보들을 내가 손수 다(!!!) 적어줘야 한다는 말이 된다. 그러다보니 나중에 제대로 문제 추가할 때도 그렇고, 서비스 시작하면 수시로 사용자가 추가될텐데 이 때도 내가 이렇게 수작업해야 한다는 소리가 되더라.


그래서 그냥 파일을 만들면 이걸 파싱해서 바로 추가하게 해야겠다^^라는 생각이 들게 되었다. 부랴부랴 데이터를 추가할 수 있는 페이지를 만들었다.

물론 야매로 구현해서 아직 파싱은 ,(공백)으로 구분하게 되어있다. 근데 좀 더 시간이 된다면 JSON 파일을 불러 와 이걸 decode하도록 하면 더 유용하겠지. 하지만 지금은 시간이 없으니, 급한 불부터 끈다! 나중에 할거야!

이렇게 해서 어찌저찌 데이터를 좀 손쉽게 그렇다 해도 아직 귀찬은 부분이 많아서 수정이 필요하긴 하다 추가할 수 있는 기능을 만들었다.

나름 디자이너 cong한테 부탁해서 그럭저럭 쓰기 좋게 만들어 뒀다 ㅎㅎ






bcrypt() 하니까 삽질한 다른 기능이 생각나버렸다.

난 원래 11시 ~ 1시 사이에 출근하기 때문에 퇴근이 늦은 편이다. 뭐… 요즘 보니 출근이 늦어서 퇴근이 늦은것 만은 아니것같긴 하지만 쨌든!

같이 Hacking on Lucy를 만드는 팀원들은 모두 퇴근하고 매일 늦게까지 있는 우리 팀원들 지나가다 보시면 밥좀 사주세요 ㅠㅠ 여느때처럼 밤에 있었는데, 이미 밤은 늦었고 남은 부분은 다른 팀원이 있어야 할 수 있길래 패스워드 변경 금방 할테니 이것만 하고 퇴근하자!라는 위험한 생각을 하게 되었다.

이 때는 이게 이렇게 고생시킬줄은 몰랐는데 말이다.


아까 앞에서 Laravel에서 로그인 매커니즘을 사용하려면 bcrypt()를 사용해야 한다고 말했었다.bcrypt() 함수를 통과하면 대충 생긴게 $2y$10$D51ouhQ49sYnhDu5aO7P9OVnkgOdxVgbMK2Xd/u3wxs.yQPzan2i6 처럼 생겼다. 난 이게 단순히 sha1이나 md5같은 해쉬값인 줄만 알고 있었다.


그래서 여느 패스워드 비교 알고리즘이랑 동일하게 아래처럼 패스워드 변경을 구현했다.

public static function change_password($current, $password, $user) {
	if(Auth::user()->password !== bcrypt($current)) {
		return "Password not correct!";
	} else {
		# change password
	}
}


여기서 $current는 사용자에게 입력받은 현재 패스워드인데, 혹시나 해서 한번 검증하는 단계로 넣어둔 조건문이었다.

그런데 아무리 올바른 패스워드를 넣어도 계속 Password not correct!만 출력했다.

내가 손가락이 고장난 것인가, 밤이 늦어서 컴퓨터도 맛이 간 것인가, 아니 내가 잠이 부족해서 제대로 생각을 못하고 있는 것인가 별의 별 생각을 다 했던 것 같다.

그래서 혹시나 하고 php artisan tinker로 테스트를 해 보았다.

>>> bcrypt('ccoma');
=> "$2y$10$.he4xLz3r2jMdGo/ctpUB.47WHIrkfVlCzY6ZyuyY2uc0lG9Dp.le"
>>> bcrypt('ccoma');
=> "$2y$10$4VeVQY3ph5.P.x7pu1HvNOZS0LxERn7zSe5NA3SXP.A021ecUGpV2"
>>> bcrypt('ccoma');
=> "$2y$10$TcRynL7mx7FSsfV7G6dS7OPVqEoST0GaOhrqUMsZ78.Whxod7O2Eu"
>>> bcrypt('ccoma');
=> "$2y$10$mLRLmPeVo1bcimSfKPUtyeM0bF70sZHN.wv8CId6G9FCWpC51JXMi"
>>> bcrypt('ccoma');
=> "$2y$10$yQF19vkspP5l/N71His5peYeY9OZLybFCtllA.nPzVweo6B.TkYK6"


두둥…!


같은 bcrypt('ccoma')를 실행했는데, 결과 값이 매번 다르다 ㅎㅎ…????

짐작건데, 아마 패스워드를 생성할 때 뭔가 변수가 들어가고, 이걸 나중에 검증할 때는 해쉬 값의 부분부분을 잘라서 어찌저찌 연산을 하면 패스워드 값이 짜란~하고 나오는… 뭐 그런 방식인것 같다. 매 번 실행할 때마다 다른 값이 나오는데 나는 단순히 string을 비교했으니 당연히 안되는 것이었다. (여기서 다시한 번 느끼는 php artisan tinker의 위대함!)

이런거 안되면 괜한 오기생겨서 끝까지 하고싶어지는데, 내가 내무덤을 판 꼴이 되었다.

그래서 결국 인터넷을 폭풍검색 해서 해결은 했다.

역시 구글 갓님 -_-bbbbb

public static function change_password($current, $password, $user) {
    if(!(Hash::check($current, $user->password))) {
        return "Password not correct!";
    } else {
        if($current === $password) {
            return "Can not use same password!";
        } else {
            $user->password = bcrypt($password);
            $user->save();
            return true;               
        }
    }
}


Hash::check()를 통해서 비교하면 된다. 그리고 또 대박인건 $user 정보를 가지고 있다면, $user->password = aaa 이런식으로 원하는 부분을 바꾸고 $user->save()를 하면 데이터베이스에 저장된다.

내가 잘 몰라서 못하는거지, Laravel 매우 잘만든 것 같다. 이번에도 다시 한 번 느꼈지만 모든 것은 유저 잘못이다.


이왕 의식의 흐름으로 간 김에 하나 더 의식의 흐름으로 가자면, 아까 사용자 데이터를 추가하는 부분이 있는데, 난 여기 코드를 아래처럼 짰다.

$user = new User;
$user->id = $request->input('id');
$user->password = bcrypt($request->input('password'));
$user->nickname = $request->input('nickname');
$user->group = $request->input('group');
$user->save();


맨 마지막 줄을 보면 $user->save()를 사용했는데, create()라는 것도 있다. 둘이 뭔 차이인가 싶겠지만, 내가 해석을 제대로 했다면, create()는 순수히 새로운 UserCreate할 때 사용하는 함수라고 한다. 반면 save()함수는 앞에서 내가 패스워드 변경을 사용할 때 사용했던 것처럼 새로운 정보를 Update를 할 때에도 사용할 수 있는 함수라고 한다. 그래서 create()를 사용하려고 하다가 그냥 save()가 더 글자가 맘에 들어서 save()로 구현했다.

이번주에 User가 이미 clear한 문제를 표시 해 주는 기능도 구현했다. 원래 회의에서 이건 나중에 구현하자! 라고 했던 부분이었던 것 같다. 그래서 나중에 시간 날 때 하려고 하다가, 디자이너가 새로운 디자인 시안을 줬는데 하나에는 해골이 뽝!, 하나에는 외계인(?)이 쨘! 하고 나타나는데 너무 귀여워서 얘네를 나타나게 하고 싶었다.

그래서 구현했다. ㅎㅎ

cong아 잘했지 칭찬해줘




아무리 봐도 너무 귀엽다. 히히!

이걸 구현할 때 또 고민했던건, 푼 문제를 어떻게 뽑아서 어떻게 전달해야 푼 문제에만 저 귀여운 캐릭터들이 나타나게 하는 것이냐 였다.

뭔가 데자뷰 같은 느낌이 들어서 지난주에 썼던 개발일지를 보니 지난주에도 똑같은 고민을 하고 있었다.

그래도 이번주엔 바보 탈출 했다.


다행스럽게도 디자이너가 CSS를 구현해서 줄 때, 푼 문제에 대해서는 따로 <div> 태그를 사용 해 만들어 주었다. 그래서 나는 푼 문제를 찾아, 만약 그 문제가 푼 문제 일 경우 해당 <div> 태그를 출력 해 주면 됬다.

그래도 여전히 푼 문제를 뽑아야 한다는 문제가 있었다.

얘는 지난주에도 말했듯, JOIN을 사용했다.

Modelsolved_list()라는 함수를 구현하고, 걔를 view인자로 넘겨 주었다. 그러면 view에서는 만약 해당 문제 인덱스가 현재 출력해야 할 문제의 인덱스와 같다면 <div>를 출력 해 주도록 구현했다.


# MainController.php

public function index() {
	$courses = Course::all();
	$phases = Challenge::all();
	$solved_list = Main::solved_list(Auth::user()->id);

	return view('main', compact('courses', 'phases', 'solved_list'));
}
# Main.php

public static function solved_list($id) {
	$result = DB::table('challenges')
			->join('challenge_solves', 'challenges.idx', '=', 'challenge_solves.chall_idx')
			->where('challenge_solves.id', $id)
			->select('challenges.idx')
			->get();

	$solved = [];
		
	foreach($result as $row) {
		$solved[] = $row->idx;
	}
		
	return $solved;
}


쨔란~
비루한 소스코드이지만 모든 것을 공개했다….ㅋㅋ




그 뒤에 홈페이지 테스트 하다 보니, 연관된 단계가 딱 한개밖에 출력이 안됬다.

확인 해 보니 내가 쿼리문을 잘못 짰었다. ㅎㅎ
푼 문제 체크하는 거 쿼리 짤때도 그랬는데, 연관된 단계들 출력할 때도 한동안 멍~ 때리고 있었다.
쿼리를… 어떻게… 뭘… 짜야하지….

구글링을 좀 했더니 기억 저편에 숨어있던 데이터베이스 + 쿼리 짜기 지식이 스물스물 나타났다.
(역시 내 뇌세포는 아직 죽지 않았나보다.)

여기서도 그렇고, 아까 bcrypt() 테스트 할 때도 그렇고 php artisan tinker가 엄청 도움이 많이 됬다.
파이썬 스크립트처럼 하나하나 실행해 보면서 잘못된 걸 바로바로 눈으로 보고 찾을 수 있어서 엄청 편리했다.


아 근데 한참 삽질한 거 쓴거 같은데, 진짜 삽질은 문제 띄우는 거였다.
이번주에 SQL Injection 문제 만들어서 사용자가 푸는 환경을 제대로 만들어보자!가 목표였다.

사실 SQL Injection 문제는 예전에 기획 통과될랑말랑 하다가 폭죽마냥 공!중!폭!파! 됬을 때가 있었는데, 그 때 기획 통과된줄 알고 설레발 치며 만들어 뒀던 문제가 있었다. 바닷가 가서 폭죽 터트리면서 고기 구워먹고싶다.

그래서 그거 살짝만 수정해서 사용했다.

…문제 안버려도 되서 다행이다ㅠㅠ


근데 다른 서버에 구현했는데, 얘를 여기에 어떻게 띄워서 어떻게 Vue.js로 쓰나 너무 고민했었다.
iframe을 두고 왜 고민했나 모르겠다.
iframe을 띄웠는데 너무 안예쁘게 딱 네모만 뜨길래 디자이너에게 부탁해서 진짜 웹페이지처럼 만들었다.

뿌듯.


여기까지만 해도 순조롭게 풀리는 줄 알았다.
그럴리가 없지 ㅎㅎ
한 단계라도 순조롭게 풀리면 Hacking on Lucy가 아니다.

어쨌든 Laravel 프레임워크에서 Vue.js를 지원한다고 했는데, 도대체 뭘 어떻게 깔아서 어떻게 호출하는지를 모르겠는 것이다. 팀원 중 한명이 Vue.js를 한참을 붙잡고 있었는데, 거기서 한거랑, Laravel에서 하는거랑 또 살짝 아니 많이 다르다.

환경 설정하는 것 부터 난관이었다.

인터넷에 찾아보니 아래처럼 하면 설치가 쨘~ 된다고 했다.

apt-get install node
apt-get install npm

npm install


근데! 왜! Laravel에 고통받은 나도! Vue에 고통받은 우리 chaem도! 아무리 해도 ERROR만 쏟아내는지…
StackOverflow의 수많은 능력자들에게 다시 한 번 감탄하며 해결할 수 있었다.
혹시나 Laravel 프레임워크를 사용하고 Vue.js를 쓰실 분들은 아래처럼 하면 무조건 될 것이라도 믿는다.

위의 과정에서 npm install까지 하면 90% 이상의 확률로 에러가 난다.
에러를 잘 읽어보면 SyntaxError라고 한다.

SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode


그럼 아래처럼 하면 된다.

apt-get install nodejs-legacy
npm install -g npm
npm install -g n
n stable
ln -sf /usr/local/n/versions/node/10.6.0/bin/node /usr/bin/node
npm rebuild node-sass
npm install vuetify --save


이렇게까지 하고 npm run dev 하면 아마 에러 없이 컴파일이 완료 될 것이다. 그 이후에는 새로운 프로젝트에 있는 welcome.blade.php에 있는 거 참고해서 헤더랑 스크립트 넣으면 쨔란~ 하고 페이지가 뜬다. 다른 에러중에 CSRF Token error도 있는데, 얘는… 다음주 삽질노트 글감을 위해 남겨준다.

어찌됬던 이렇게 우여곡절 끝에 연동까지 하고 뭔가 블록 친 네모도 보이고 글도 보이는데 얘네가 여기저기 날아다녀서 이제는 CSS 맞추느라 고생중이다 ㅠㅠ

chaem 지못미… 내일 함께 고생 할 cong도…




삽질 일기 3주차의 끝

항상 개발일지를 쓸 때마다 느끼는 건데, 고민은 정말 한참 하고 아... 왜 안되는 것인가 이러면서 에러를 해결하기 까지의 과정은 엄청 오래걸렸는데 글로 쓰니까 별거 아닌 것 같다. 거기다 결과로 나온 코드도 엄청 길면 와 뭔가 대단한걸 해냈어! 이런 느낌일 것 같은데, 막 길어야 2줄, 3줄 이러니까 허무하다.

뭐 다른 사람들은 몰라도, 팀장님과 이사님은 우리가 삽질한 걸 지켜보고 계시니, 나중에 팀장님이나 이사님맛난거 많이 사주시지 않을까 싶다. ㅎㅎ


오늘까지 드디어 홈페이지의 어지간한 기능은 모두 구현 되었고, Vue.js까지 연동해서 문제 페이지도 띄우고 나니까 뭔가 틀이 보이기는 한다.

처음에 Hacking on Lucy라는 이야기를 들었을 때는 이걸 어떻게 하지 싶었다.

내가 학교 다니며 뭘 어떻게 공부해야 하나라는 고민을 해봤던 사람이기에, 이러한 교육 콘텐츠에 대한 필요성은 예전부터 느끼고 있었지만서도, 섣불리 이런걸 만들어 보자 라는 생각은 전혀 하지 못했다. 아직도 뭘 어떻게 공부해야 할지 잘 모르겠을 뿐더러, 교육을 하려면 잘 알아야 하는데 내가 잘 알고 있지는 않기 때문이다.

그런데 지금은 하울의 움직이는 성 마냥 금방이라도 무너질 듯 보이기는 하는데 어찌됬건 목적지로 한발한발 내딛고 있는 모습을 보니, 언젠가는 영화 마지막에 나왔던 튼튼한 진짜 성이 되지 않을까 싶다.

정신차리고 보니 내일이 아니고 오늘이 목요일이다.(젠장)
일주일이 언제 이렇게 지나갔는지 너무 순식간에 지나갔다.

내일은 오늘은 신나는 회의니까 이제 집에 가야겠다. ㅎㅎ

comments powered by Disqus