문제 

https://school.programmers.co.kr/learn/courses/30/lessons/12906

 

 

코드

function solution(arr)
{
    var answer = [];

    for(let i = 1; i <= arr.length; i++) {
        if(arr[i-1] != arr[i] ) {
            answer.push(arr[i-1]);
        }
    }
    
    return answer;
}

 

 

후기

너무 빨리 풀어서 딱히 후기랄 것도 없지만 그냥 보자마자 이렇게 짜면 되는 거 아닌가..? 생각했지만 앞 문제의 for문에 시달려서 혹시 for문 쓰면 안되려나? 싶어서 풀긴 했지만 찾아보니 대부분 나랑 동일하게 풀었고 더 짧게 작성하신 분은 fliter를 쓰셨다.

fliter...생각도 못했다... 공부한다...

 

푸는 데 걸린 시간

4시간

 

 

문제

https://school.programmers.co.kr/learn/courses/30/lessons/92334?language=javascript

 

 

이번에는 금방 푼 거 같은데 시간 초과 해결하느라 시간이 좀 걸렸다. 찾아보니 시간 초과 때문에 통과 못 하신 분들이 많은 것 같은데 문제 특성상 반복문을 사용하는 방식을 쉽게 떠올리게 되어 있어서 그런 듯하다. 근데 javascript를 쓰는 경우에는 map, reduce를 활용하라고 대놓고 낸 문제인 것 같다. 문제 보자마자 빤히 보이길래 알고 있었는데도 애써 무시하고 3중첩 for문 돌렸다가 코드를 거의 다시 짜야했다. (map, reduce가 아직 낯설어서 쓰기 싫었음)

 

 

시간초과 실패 코드

/*  1. 캐릭별 신고 받은 횟수 카운트하고
    2. 신고받은 횟수가 k 이상인 경우
    3. 신고자에게 +1
    (같은 유저가 같은 유저 여러 번 신고해도 1번만 카운트)
*/

function solution(id_list, report, k) {
    var answer = [];
    var blist = []; // 신고 당한 유저
    var cnt = []; // 신고 당한 횟수
    
    report = [...new Set(report)]; // 같은 유저가 같은 유저 신고 카운트 1번만 되도록 중복 제거
    
    answer = new Array(id_list.length).fill(0);
    
    for (let i=0; i < report.length; i++) {
        blist.push(report[i].split(" ",2)[1]);
    }
    
    cnt = blist.reduce((arr, cur) => { 
        arr[cur] = (arr[cur] || 0)+1;
        return arr;
    }, {});
    

    for (let j=0; j < (Object.keys(cnt)).length; j++) { 
        if (Object.values(cnt)[j] >= k) { // 신고 횟수가 이상이면
            for(let m=0; m < report.length; m++) {
                if (Object.keys(cnt)[j] == report[m].split(" ",2)[1]) { // 신고당한 유저
                    for(let n=0; n < id_list.length; n++) {
                        if (id_list[n] == report[m].split(" ",2)[0]) { // 신고한 유저
                            answer[n] += 1;
                        }
                    }
                }
            }
        }
    } 

    return answer;
}

 

정답코드를 보고나니 for문 쓰는 건 확실히 괜히 복잡해지고 좋은 코드는 아니다.

위의 코드가 평소 내가 짜던 스타일인데 내 코드는 뭔가 전부터 느꼈지만 아는 거에서 어떻게든 돌리려는 느낌이 강한, 좀 고집있는..? 스타일인 거 같다.

고쳐야 되는 스타일이긴 하다...

두 번째 문제 푼 건데 벌써 코딩 테스트가 꽤 도움이 되는 것 같다고 느끼고 있다. 

역시 카카오 코테는 좀 어렵지만 왜 코테보는 지 알 거 같기도 하다.

시간 초과 실패라는 걸 알았을 때부터는 map을 써서 어떻게든 for문 하나만 빼보자, 라는 생각으로 머리를 굴렸다.

근데 map을 써도 for문이 줄어들 수 있는 코드는 도저히 생각이 안나서 아래 코드를 참고했다.

(map을 어떻게 써야 for문을 안 쓸 수 있는 거지?? 라는 생각이 자꾸 들었다.)

 

 

코드 (정답)

https://intrepidgeeks.com/tutorial/programmer-get-report-results-javascript-ver

 

프로그래머스 - 신고 결과 받기(JavaScript Ver.)

인트로 Programmers에서 독학 첫 번째로 도전한 문제이다. 이걸 해결해보고자 한다. 문제 Level: 1 언어: Javascript 문제보기 입력값 id_list: 이용자의 id가 담긴 문자열 배열 report: 각 이용자가 신고한 이

intrepidgeeks.com

 

 

 

후기

이 문제는 해결한 게 아니니까 정답을 제출 하진 않았다.

나중에 다시 풀어볼 생각이다.

코테 처음이면서 왜 이렇게 몇 시간씩 걸리는 문제부터 푸냐고 생각할 수도 있는데 풀고 싶어서 푸는 게 아니라 1단계 문제에 나와있는 문제 순서대로 푸는 건데 거기에 카카오 코테가 있을 뿐이다.

이게 젤 쉬운 단계라고? 라는 의문이 이 두 번째 문제 풀 때부터 들어서 찾아보니 사람들도 이게 1단계 문제인 게 이상하다고 생각하시더라.

원래 1단계 문제는 언어 기초만 알아도 풀 수 있는 수준이어야되는데 어렵다고.

나는 코테가 1단계도 이 정도 수준인 줄 알았다.

나중에는 얼마나 어려울 지 겁먹고 있었는데 생각해보니까 카카오 입사문제가 1단계 일리가 없지..

다음 문제부터는 1단계에서 카카오 일단 빼고 쉬운 문제부터 푸는 걸로..

 

문제

https://school.programmers.co.kr/learn/courses/30/lessons/118666?language=javascript

 

 

푸는 데 걸린 시간

5시간

 

 

코드

function solution(survey, choices) {
    var answer = '';
    var arr = { R : 0, T : 0, C : 0, F : 0, J : 0, M : 0, A : 0, N : 0 };
    
    for (let i=0; i < survey.length; i++) {
        if(choices[i] < 4) { 
            arr[survey[i].slice(0,1)] += 4 - choices[i];
        } else if(choices[i] > 4) {
            arr[survey[i].slice(1,2)] += choices[i] - 4;
        }
    }
    
    for(let j = 1; j <= Object.keys(arr).length; j+=2) {
        if (Object.values(arr)[j-1] > arr[Object.values(arr)[j]]) {
            answer += Object.keys(arr)[j-1];
        } else if (Object.values(arr)[j-1] < Object.values(arr)[j]){
            answer += Object.keys(arr)[j];
        } else { // 동점. 알파벳 순
            if(Object.keys(arr)[j-1] < Object.keys(arr)[j]) {
                answer += Object.keys(arr)[j-1];
            } else {
                answer += Object.keys(arr)[j];
            }
        }
    } 
    return answer;
}

 

 

후기

일단 문제 읽는데 1시간 걸렸다.

문제가 이해가 잘 안돼서 엄청 애 먹었다.

그리고 3시간은 코드 작성하는 데 걸렸는데 나와의 싸움이었다.

코드를 작성하면서도 '이렇게 하면 될 거 같긴한데 너무 어렵게 짜는 거 같은데?' 라는 생각이 들어서 썼다 지웠다하면서 좀 더 단순하게 짤 수 있을 거 같은데...를 반복해서 코드가 길진 않은 데 오래 걸렸다.

코딩테스트는 어쨌든 간결하고 단순하게 짤 수록 좋으니까 길고 복잡하게 짜고 싶지 않았는 데 해결하고 나서 구글링해보니 내 코드도 좋은 코드는 아니다.

나머지 시간은 테스트 통과가 안돼서 헤매는 시간이었는데 알고보니 내가 문제 이해를 잘못 한 거였다.

그러니까 그냥 문제 이해를 못해서 시간을 엄청 잡아먹었다....

(점수를 1,2,3/3,2,1 이렇게 부여하려면 4를 빼줘야되는 데 4를 안 빼줘서 그런 거 였음)

 

프로그래머스 해보게 된 이유

 

컴퓨터공학과를 다니면서 코딩 테스트 하는 사람들이 주변에 많았는데 그런 사람들을 보면서 항상 생각하던 게 있다.

 

코딩 테스트 = 코딩 실력 인가?

 

코딩 테스트를 잘하면 과연 코딩을 잘한다고 할 수 있는 걸까?

 

이런 생각을 하면서 의도적으로 코딩 테스트를 피해왔다.

 

의문이 든다는 것 자체가 내 생각에는 관련 없어 보였기 때문이다.

 

아예 관련이 없진 않겠지, 근데 필수는 아닌 듯- 하고 살았고 지금 회사도 코딩 테스트를 보지 않는 회사였기 때문에 정말 잊고 살았다.

 

근데 이제 취업에 쫓기지 않다보니 좀 여유가 생겼는 지 이 의문을 증명하고 싶어졌다.

 

코딩 테스트가 정말 코딩 실력에 도움이 되는 지, 코딩 테스트를 잘 하게 되면 알고리즘을 더 잘 짜게 되는 지 궁금해졌고 이를 알아보려면 코딩 테스트를 풀어봐야하기 때문에 시작하게 됐다.

 

어차피 웬만한 회사는 다 코딩 테스트를 보니까 해두면 나쁘진 않을 거 같기도 했고 정말 알고리즘 짜는 데 도움이 된다면 현재 회사 일도 더 잘 할 수 있게 되지 않을까 싶어서.

 

 

Javascript 선택 이유

 

나는 최애 언어가 꽤 자주 바뀌는 편인데 변천사를 설명하자면 아래와 같다.

 

첫 최애 언어 - C언어 (2017 - 2018)

이유 : 내 첫 언어가 C언어였다. (요즘에는 Python부터 배우지만 라떼는 C언어 였음)

C언어 배우고 다음 학기에 JAVA를 배우니까 JAVA는 처음부터 마음에 안 들었다.

일단 print문도 printf로 쓰면 될 걸, system.out.println 이딴 식으로 써야하고 import나 기본 클래스 작성도 어찌나 많고 긴지 오죽하면 이클립스에 새 파일 생성할 때 기본 클래스 코드 작성해주는 옵션이 있겠나.

그리고 객체 지향인 것도 마음에 안 들었다.

그냥 함수 쓰면 될 걸 클래스까지 써야하니까 마음에 드는 구석이 없었다.

그러니 상대적으로 C언어가 천사로 보였다. 

(어느 정도 였냐면 면접 볼 때 JAVA를 훨씬 오래 쓰셨는데 면접은 C언어로 보시네요? 라는 질문도 들었음)

 

두 번째 최애 언어 - Python (2019 - 2021)

이유 : 학교에서 Python만 써서.

근데 Python이 쉽기도 하고 학교에서는 코딩 수업이 전부 딥러닝 수업이었어서 Python을 쓸 수 밖에 없었다.

자주 쓰다보니 몸에 익어서 최애 언어가 됐다. 

그래서 졸작도 백엔드지만 Django로 개발하게 됐다.

 

현재 최애 언어 - Javascript (2022 -)

이유 : 회사에서 Javascirpt만 써서.

생각해보니 쓰기 편한 언어가 최애 언어가 되는 듯 하다.

 

서론이 길었는데 결론적으로 현재 최애 언어인 Javascript로 프로그래머스를 풀어보겠다는 뜻이다.

작년에 풀었으면 아마 Python으로 했을 듯.

 

 

여는 글


 

저번에 localhost 접속하기 까지 진행했습니다.

 

사실 이번엔 페이지 라우팅(url?)에 대한 이해부터 하고 싶었습니다.

 

서브 페이지로 이동하려면 경로가 어떻게 되는 지 도저히 눈에 안 보였기 때문인데 Node.js, Django를 써봤는데 이렇게까지 페이지 라우팅이 뭔지 모르겠는 프레임워크는 또 처음이네요...

(Django는 url setting 설정 파일이 있거나 Node.js는 그냥 router 파일에 경로만 추가해주면 됐었는데..)

 

어쩔 수 없이 index 페이지에서 DB값을 출력해보기로 했습니다.

 

그래서 일단 이건 다음 시간으로 넘기고 DB 연결부터 하게 됐습니다.

 

 

DB 연결


 

그 전에!! CI는 초기에는 배포모드로 되어있습니다.

 

다른 프레임워크는 배포 전에 따로 설정해주지 않는 이상 개발자모드로 되어있는데 에러 생겨도 Woops! 페이지만 뜨고 원인을 안 알려줘서 당황했었기 때문에 이 설정부터 해보겠습니다.

 

https://itadventure.tistory.com/368 

 

코드이그나이터4 개발자 모드 활성화하기

코드이그나이터4에서는 기본적으로 약간만 오류가 발생해도 Whoops! 라는 안내 멘트와 함께 오류 결과를 알려주지 않습니다. 이는 해킹을 방지하기 위한 시스템적인 조치인데요. 해커들이 사이트

itadventure.tistory.com

 

 

App/Config/Database.php

<?php

namespace Config;

use CodeIgniter\Database\Config;

/**
 * Database Configuration
 */
class Database extends Config
{
    /**
     * The directory that holds the Migrations
     * and Seeds directories.
     *
     * @var string
     */
    public $filesPath = APPPATH . 'Database' . DIRECTORY_SEPARATOR;

    /**
     * Lets you choose which connection group to
     * use if no other is specified.
     *
     * @var string
     */
    public $defaultGroup = 'default';

    /**
     * The default database connection.
     *
     * @var array
     */
    public $default = [
        'DSN'      => '',
        'hostname' => 'localhost',
        'username' => 'DBMS 유저명',
        'password' => 'DBMS 비밀번호',
        'database' => 'DB 명',
        'DBDriver' => 'MySQLi',
        'DBPrefix' => '',
        'pConnect' => false,
        'DBDebug'  => (ENVIRONMENT !== 'production'),
        'charset'  => 'utf8',
        'DBCollat' => 'utf8_general_ci',
        'swapPre'  => '',
        'encrypt'  => false,
        'compress' => false,
        'strictOn' => false,
        'failover' => [],
        'port'     => 3307, // 기본포트는 3306이나 저의 경우 다른 DB가 해당 포트를 사용중이므로 3307로 지정했습니다.
    ];

    /**
     * This database connection is used when
     * running PHPUnit database tests.
     *
     * @var array
     */
    public $tests = [
        'DSN'      => '',
        'hostname' => '127.0.0.1',
        'username' => '',
        'password' => '',
        'database' => ':memory:',
        'DBDriver' => 'SQLite3',
        'DBPrefix' => 'db_',  // Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS
        'pConnect' => false,
        'DBDebug'  => (ENVIRONMENT !== 'production'),
        'charset'  => 'utf8',
        'DBCollat' => 'utf8_general_ci',
        'swapPre'  => '',
        'encrypt'  => false,
        'compress' => false,
        'strictOn' => false,
        'failover' => [],
        'port'     => 3306,
    ];

    public function __construct()
    {
        parent::__construct();

        // Ensure that we always set the database group to 'tests' if
        // we are currently running an automated test suite, so that
        // we don't overwrite live data on accident.
        if (ENVIRONMENT === 'testing') {
            $this->defaultGroup = 'tests';
        }
    }
}

 

다른 건 건드릴 필요 없었고 default에 한글로 기재해 놓은 부분만 수정하면 됩니다.

 

 

 

App/Controllers/Home.php

<?php


namespace App\Controllers;

use App\Models\sample_model;



class Home extends BaseController
{

    
     /* public function __construct(){ 
        // parent::__construct(); // BaseController 상속 받을 때는 이 코드 사용 안함!
        $this->load->model('show_db'); // ci3 버전 모델 로드 코드
    } */


    public function index()
    {
        $sampleModel = model(sample_model::class);
        $db      = \Config\Database::connect();
        $builder = $db->table('cidb');
        $query   = $builder->get();  // Produces: SELECT * FROM mytable
        foreach ($query->getResult() as $row) {
            echo $row->id, "\n"; 
            echo $row->title;

        }
        return view('show_db'); // App/Views/show_db.php
    }
}

 

위 코드를 작성하기까지 꽤 구글링을 했었던 터라...

빌더는 아래 사이트 참조!

http://ci4doc.cikorea.net/database/query_builder.html

 

쿼리 빌더 클래스 — CodeIgniter 4.1.9 documentation

Insert된 행의 수, 실패시 false

ci4doc.cikorea.net

 

그리고 모델 load 하는데도 ci3이랑 4랑 문법 자체가 달라서 ci3 코드보다가 에러 뱉은 적이 한 두번이 아닙니다. ㅜㅜ (주석에 보면 있어요)

 

구글링해보면 ci3 자료만 많고 ci4가 없어서 꽤 어렵다고 느끼긴 했고 사실 저 위의 코드가 맞는 지는 모르겠습니다.

(약간 코드 때려놓고 돌아가긴 하네, 하는 느낌이라 불필요한 코드가 섞여있을 수도 있습니다.)

 

이제 마지막으로 아래 코드를 작성해주면 됩니다.

 

App/Models/sample_model.php

<?php

namespace App\Models;


use CodeIgniter\Model;

class SampleModel extends Model {
    protected $primaryKey = "title";
}

?>

 

 

 

닫는 글


 

이렇게 하니까 db 값도 화면에 잘 나오고 하긴 하는데 여전히 이해 안되는 url 규칙 때문에 찝찝해서 다음 번엔 꼭 접속 경로를 파악하는 걸로 하겠습니다.

 

영원히 index 페이지에서만 놀 수는 없으니까요..

'Codeigniter4' 카테고리의 다른 글

[PHP8, Codeigniter4] VSCODE 환경 세팅하기  (0) 2022.05.05

 

 

여는 글


 

Codeigniter로 개발해야 할 일이 생겨 노트북에 개발 환경을 세팅해야 합니다.

 

다른 컴에도 깔 게 될 일이 생길 지 모르니 정리해두는 차원에서 작성해보겠습니다.

(환경 세팅은 처음에 해놓고 안 하니까 나중에는 기억이 안나서...)

 

 

 

 

환경세팅


 

 

1. https://ssimplay.tistory.com/557

 

[vs code] 비주얼 스튜디오 코드 php 개발환경 설정하기

vs code에서 php 작업을 하려면 약간의 준비가 필요한데요. 차근차근 같이 해봅쉬다~! 1. php 설치 [coding/etc] - [PHP] php 설치하기 (윈도우) [PHP] php 설치하기 (윈도우) 오늘은 php를 설치해보겠습니다. 1...

ssimplay.tistory.com

 

이 포스팅에서 PHP 설치 방법 링크도 타고 가서 그대로 따라하면 됩니다.

 

 

2. 환경변수 설정

 

환경변수에서 C:\Bitnami\wampstack-8.1.5-0\php (버전 별로 상이) 추가해주기

 

참고 : https://velog.io/@kangpungyun/%EC%BD%94%EB%93%9C%EC%9D%B4%EA%B7%B8%EB%82%98%EC%9D%B4%ED%84%B04-%EC%84%A4%EC%B9%98-%EC%A4%91-%EC%97%90%EB%9F%AC

 

 

3. Codeigniter4 설치하기

 

구글에 많이 나와있으므로 생략하지만 공식 홈페이지에서 그냥 다운로드 받으면 됩니다.

 

그러면 C:\Users\유저명\Downloads\codeigniter4-CodeIgniter4-v4.1.9-0-g202f41a

 

이렇게 codeigniter4 파일이 생깁니다.

 

 

4. VSCODE에 디렉터리 가져오기

 

보통 프레임워크쓰면 기본 파일 구조가 생성되어 있는 디렉터리에서 작업하니까 디렉터리를 옮겨야 합니다.

 

3.에서 다운 받은 codeigniter4을 들어가보면 codeigniter4-CodeIgniter4-v4.1.9-0-g202f41a/codeigniter4-CodeIgniter4-v4.1.9-0-g202f41a 이런 구조인데 이 파일을 vscode에 넣으면 됩니다.

 

하위에 app/controllers ... 등 익숙한 mvc 구조의 파일이 보이게 됩니다.

 

 

5. vscode terminal에서 php spark serve 치면 localhost:8080으로 접속하라고 뜨면 성공!

(php 인식 안된다는 식의 에러뜨면 환경변수 잘못 설정한 겁니다.)

 

참고 : http://ci4doc.cikorea.net/installation/running.html

 

 

닫는 글


 

저는 Django 경험이 있어서 그런지 디렉터리 구조는 생각보다 이해가 쉽네요.

 

PHP는 개발 해봐야 알겠지만 생각보다 코드가 직관적이라서 쉬운 듯? 어려운 듯? 하고... 

 

해당 카테고리는 배포까지 보고 깊게 개발할 목적이 아닌 localhost환경에서 기본 CRUD를 익히기 위한 아주 기본적인 개발 기록만 남길 예정입니다.

(간단한 페이지 2~3개, db 연동+사용 해보기 정도)

 

코드이그나이터 구조 이해를 위한 공부 카테고리입니다. 

 

 

 

'Codeigniter4' 카테고리의 다른 글

[PHP8, Codeigniter4] MariaDB 연결 + DB 출력  (0) 2022.05.08

 

 

 

개발 전 과정

그 동안의 개발과정은 해당 카테고리 + Node.js 카테고리에서 볼 수 있습니다.

 

 

 

백엔드

 

backend/app.js

var createError = require('http-errors');
var express = require('express');
var path = require('path');
const history = require('connect-history-api-fallback');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
const cors = require("cors");
const mongoose = require('mongoose');
var session = require('express-session');
var indexRouter = require('./routes/index');

require('dotenv').config();

var app = express();



// view engine setup

var views = [
  //path.join(__dirname, '../public', 'index.html')
  path.join(__dirname, 'views')
]

app.set('views', views);
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.cookie_secret));

app.use(session({
  secret : process.env.session_secret,
  resave : false,
  saveUninitialized : false,
  cookie : {
    maxAge : (3.6e+6)*12,
    httpOnly : false
  },
  store : require('mongoose-session')(mongoose)
}))

app.use('/', indexRouter);
app.use(history());

app.use(express.static(path.join(__dirname, 'public')));

app.use(
  cors({
    origin : 'http://olrang.shop:3000',
    credentials : true,
  })
);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});




  // mongodb 미들웨어
mongoose.connect(  // DB 연결
  'mongodb://olrang.shop:27017/mydbname',  // DB 주소
  { useNewUrlParser : true, useUnifiedTopology : true,},
  function(err) {
    if(err){
      console.error('mongodb connection error!!!', err);
    }
    console.log('mongodb connected!!')  // 연결 성공시 출력되는 메시지
  }
);





var userControll=require('./routes/index.js');

app.use('/signup',userControll); 

app.use('/login', userControll);

app.use('/logout', userControll);

app.use('/aptitude',userControll); 

app.use('/secession',userControll); 

app.use('/api/mypage',userControll);

app.use('/mypage',userControll);




module.exports = app;

 

Express, cookie, mongodb, express-session을 사용했습니다.

 

cookie, session의 secretkey는 dotenv를 사용해서 분리했습니다.

 

https가 아닌 http를 사용했기 때문에 보안옵션은 사용하지 않았습니다.

 

 

 

backend/routes/index.js

var express = require('express');
var path = require('path');
var router = express.Router();
router.use(express.urlencoded({extended:true}));
router.use(express.json())
var User = require('./mongodbuser');  // DB 스키마 파일 참조
var Product = require('./mongodbproduct'); // DB 스키마 파일 참조
var Order = require('./mongodborder'); // DB 스키마 파일 참조
const crypto = require('crypto'); // 패스워드 암호화 모듈


/* GET home page. */
router.get('/', function(req, res, next) {
  res.sendFile(path.join(__dirname, '../public', 'index.html'));
  //res.render('index', { title: 'Express' });
});

router.post('/signup', (request, response) => { //회원 가입

  var ck = true;
  if(request.body.id == request.body.password){  //request.body는 웹 input 태그에서 값 가져오는 기능. body 뒤에는 input 태그의 name 값을 써야함
    try{
      response.send("<script>alert('아이디와 비밀번호는 서로 달라야 합니다.');location.href='/signup';</script>");
      }catch (exception) {
        response.redirect('/signup');
      }
    ck= false;
  }
  
  if (request.body.id == ''){  // 공백 입력 방지
    try{
      response.send("<script>alert('id가 입력되지 않았습니다.');location.href='/signup';</script>");
      }catch (exception) {
        response.redirect('/signup');
      }
  }

  if (request.body.password == ''){
    try{
      response.send("<script>alert('password가 입력되지 않았습니다.');location.href='/signup';</script>");
      }catch (exception) {
        response.redirect('/signup');
      }
  }

  if (request.body.name == ''){
    try{
      response.send("<script>alert('name이 입력되지 않았습니다.');location.href='/signup';</script>");
      }catch (exception) {
        response.redirect('/signup');
      }
  }

  if (request.body.phone == ''){
    try{
      response.send("<script>alert('phone number가 입력되지 않았습니다.');location.href='/signup';</script>");
      }catch (exception) {
        response.redirect('/signup');
      }
  }

  User.find((err, users) => { //id 중복체크 
      if(users.id == request.body.id){  // users는 DB 유저 목록  
          ck = false;
          try{
            response.send("<script>alert('이미 존재하는 아이디입니다.');location.href='/signup';</script>");
            }catch (exception) {
              response.redirect('/signup');
            }
        }
      });

    if(ck==true){
      var password = request.body.password;
      crypto.randomBytes(64, (err, buf) => {  // 패스워드 암호화
        const salt = buf.toString('base64')
        crypto.pbkdf2(password, salt, 107529, 64, 'sha512', (err, key) => {
          User.create ({ //DB 생성
            id:request.body.id,
            password:(key.toString('base64')),
            salt : salt,  // salt 값 저장
            name:request.body.name,
            phone:request.body.phone,
          }); 
        });
      });
    


      response.redirect('/login');
    }



});



router.post('/login', (req, res) => {

  User.findOne({id : req.body.id},(err, users) => { // id를 먼저 체크해서
    if(!users || err){ //id가 DB에 없으면(즉, id가 틀렸거나 회원가입이 안되어 있거나) 
      try{
        res.send("<script>alert('아이디가 틀렸습니다.');location.href='/login';</script>");
        }catch (exception) {
          res.redirect('/login');
        }
      
    }
    else {
    var cpassword = req.body.password; // 사용자가 입력한 패스워드를 가져와서
    crypto.randomBytes(64, (err, buf) => {
      crypto.pbkdf2(cpassword, users.salt, 107529, 64, 'sha512', (err, key) => {
        cpassword = key.toString('base64') // DB에 저장했던 동일한 방식으로 암호화 
        
        if (users.password == cpassword) { // 저장된 패스워드와 암호화된 입력받은 패스워드 비교
          // res.header('Access-Control-Allow-Origin', 'olrang.shop:3000');
          // res.header("Access-Control-Allow-Credentials", true);
          /*res.cookie('session', users.name, {
          maxAge : (3.6e+6)*12 //12시간
          // secure : true,  // https 쓸 때 사용 가능한 보안 옵션
          // sameSite : 'none', //secure 쓸 때 사용 가능한 옵션
          // domain : 'olrang.shop:3000/login',
          // httpOnly : true,  // js에서 쿠키에 접근 못하게 하는 옵션
        }); */
            req.session.userinfo = users.phone;
           
        res.redirect('/');
        } else { // 패스워드 틀렸을 때
          try{
            res.send("<script>alert('비밀번호가 틀렸습니다.');location.href='/login';</script>");
            }catch (exception) {
              res.redirect('/login');
            }
          
        }
      });
    });
  }
  })
}) 

router.get('/logout', (req, res) => {
      if(!req.session) {  // 세션 없을 때
        res.redirect('/');
      } else {
        req.session.destroy(()=>{
          res.clearCookie('connect.sid');
          res.redirect('/');
      });
      }
})

router.get('/api/mypage', (req, res) => { //마이페이지에 띄울 db 정보 가져오기
    var mydata = {phone : req.session.userinfo};
    res.json(mydata);
})

router.post('/mypage', (req, res) => { //마이페이지 정보 수정
    if(req.body.phone == ''){
      try{
        res.send("<script>alert('값을 입력해주세요');location.href='/mypage';</script>");
        }catch (exception) {
          res.redirect('/mypage');
        }
    } else { 
        users.updateOne({$set : {phone : req.body.phone}}).exec();
        res.redirect('/');
    }
    res.redirect('/');

})



router.post('/secession', (req, res) => { // 탈퇴
  User.findOne({phone : req.sesssion.userinfo},(err, users) => { 
      if(!req.sesssion.userinfo || err) {  // 정보/쿠키 없거나 에러 났을 때
        res.redirect('/');
      } else {
        users.deleteOne();
        req.session.destroy(()=>{
        res.clearCookie('connect.sid');
        res.redirect('/');
      }); 
    }
  })
})

router.post('/aptitude', async (req, res) => {
  // 프론트에서 결제 api 먼저 실행 후 
  try {
    // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
    
    const imp_uid = req.body.imp_uid;
    const merchant_uid = req.body.merchant_uid;


    // 액세스 토큰(access token) 발급 받기
    const getToken = await axios({
      url: "https://api.iamport.kr/users/getToken",
      method: "post", // POST method
      headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
      data: {
        imp_key: "2406300329155625", // REST API키
        imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
      }
    });
    const { access_token } = getToken.data.response; // 인증 토큰


      // imp_uid로 아임포트 서버에서 결제 정보 조회
      const getPaymentData = await axios({
        url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
        method: "get", // GET method
        headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
      });
      const paymentData = getPaymentData.data.response; // 조회한 결제 정보

      // DB에서 결제되어야 하는 금액 조회
      const order = await Product.findOne({name :"aptitude"});
      const amountToBePaid = order.amount; // 결제 되어야 하는 금액


      // 결제 검증하기
      const { amount, status } = paymentData;
      if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
        await Order.create({  // DB에 결제 정보 저장
          number : merchant_uid,
          name : req.cookies.session,
          product : req.body.name,
          amount : req.body.amount
        }); 


        switch (status) {
          case "paid": // 결제 완료
            console.log("결제 성공");  
          //res.send({ status: "success", message: "일반 결제 성공" });
            break;
        }
      } else { // 결제 금액 불일치. 위/변조 된 결제
        console.log("위조된 결제 시도");
        //throw { status: "forgery", message: "위조된 결제시도" };
      }
    } catch (e) {
      res.status(400).send(e);
    }
  });

  router.post('/homelover', async (req, res) => {
    // 프론트에서 결제 api 먼저 실행 후 
    try {
      // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
      
      const imp_uid = req.body.imp_uid;
      const merchant_uid = req.body.merchant_uid;
  
  
      // 액세스 토큰(access token) 발급 받기
      const getToken = await axios({
        url: "https://api.iamport.kr/users/getToken",
        method: "post", // POST method
        headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
        data: {
          imp_key: "2406300329155625", // REST API키
          imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
        }
      });
      const { access_token } = getToken.data.response; // 인증 토큰
  
  
        // imp_uid로 아임포트 서버에서 결제 정보 조회
        const getPaymentData = await axios({
          url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
          method: "get", // GET method
          headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
        });
        const paymentData = getPaymentData.data.response; // 조회한 결제 정보
  
        // DB에서 결제되어야 하는 금액 조회
        const order = await Product.findOne({name :"homelover"});
        const amountToBePaid = order.amount; // 결제 되어야 하는 금액
  
  
        // 결제 검증하기
        const { amount, status } = paymentData;
        if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
          await Order.create({  // DB에 결제 정보 저장
            number : merchant_uid,
            name : req.cookies.session,
            product : req.body.name,
            amount : req.body.amount
          }); 
  
  
          switch (status) {
            case "paid": // 결제 완료
              console.log("결제 성공");  
            //res.send({ status: "success", message: "일반 결제 성공" });
              break;
          }
        } else { // 결제 금액 불일치. 위/변조 된 결제
          console.log("위조된 결제 시도");
          //throw { status: "forgery", message: "위조된 결제시도" };
        }
      } catch (e) {
        res.status(400).send(e);
      }
    });


    router.post('/past', async (req, res) => {
      // 프론트에서 결제 api 먼저 실행 후 
      try {
        // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
        
        const imp_uid = req.body.imp_uid;
        const merchant_uid = req.body.merchant_uid;
    
    
        // 액세스 토큰(access token) 발급 받기
        const getToken = await axios({
          url: "https://api.iamport.kr/users/getToken",
          method: "post", // POST method
          headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
          data: {
            imp_key: "2406300329155625", // REST API키
            imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
          }
        });
        const { access_token } = getToken.data.response; // 인증 토큰
    
    
          // imp_uid로 아임포트 서버에서 결제 정보 조회
          const getPaymentData = await axios({
            url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
            method: "get", // GET method
            headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
          });
          const paymentData = getPaymentData.data.response; // 조회한 결제 정보
    
          // DB에서 결제되어야 하는 금액 조회
          const order = await Product.findOne({name :"pastpotion"});
          const amountToBePaid = order.amount; // 결제 되어야 하는 금액
    
    
          // 결제 검증하기
          const { amount, status } = paymentData;
          if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
            //const useraddress = User.findOne({name : req.cookies.session});
            await Order.create({  // DB에 결제 정보 저장
              number : merchant_uid,
              name : req.cookies.session,
              product : req.body.name,
              amount : req.body.amount
            }); 
    
    
            switch (status) {
              case "paid": // 결제 완료
                console.log("결제 성공");  
              //res.send({ status: "success", message: "일반 결제 성공" });
                break;
            }
          } else { // 결제 금액 불일치. 위/변조 된 결제
            console.log("위조된 결제 시도");
            //throw { status: "forgery", message: "위조된 결제시도" };
          }
        } catch (e) {
          res.status(400).send(e);
        }
      });


      router.post('/friendadd', async (req, res) => {
        // 프론트에서 결제 api 먼저 실행 후 
        try {
          // const { imp_uid, merchant_uid } = req.body; // req의 body에서 imp_uid, merchant_uid 추출
          
          const imp_uid = req.body.imp_uid;
          const merchant_uid = req.body.merchant_uid;
      
      
          // 액세스 토큰(access token) 발급 받기
          const getToken = await axios({
            url: "https://api.iamport.kr/users/getToken",
            method: "post", // POST method
            headers: { "Content-Type": "application/json" }, // "Content-Type": "application/json"
            data: {
              imp_key: "2406300329155625", // REST API키
              imp_secret: "81a24245bfda5c8ef9dd9098e973245259ca027de69f48bbee3d06bc49d9f57355a1fe6d6a663556" // REST API Secret
            }
          });
          const { access_token } = getToken.data.response; // 인증 토큰
      
      
            // imp_uid로 아임포트 서버에서 결제 정보 조회
            const getPaymentData = await axios({
              url: `https://api.iamport.kr/payments/${imp_uid}`, // imp_uid 전달
              method: "get", // GET method
              headers: { "Authorization": access_token } // 인증 토큰 Authorization header에 추가
            });
            const paymentData = getPaymentData.data.response; // 조회한 결제 정보
      
            // DB에서 결제되어야 하는 금액 조회
            const order = await Product.findOne({name :"friendadd"});
            const amountToBePaid = order.amount; // 결제 되어야 하는 금액
      
      
            // 결제 검증하기
            const { amount, status } = paymentData;
            if (amount === amountToBePaid && req.cookies.session) { // 결제 금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
              // const useraddress = User.findOne({name : req.cookies.session});
              await Order.create({  // DB에 결제 정보 저장
                number : merchant_uid,
                name : req.cookies.session,
                // address : useraddress.address,
                product : req.body.name,
                amount : req.body.amount
              }); 
      
      
              switch (status) {
                case "paid": // 결제 완료
                  console.log("결제 성공");  
                //res.send({ status: "success", message: "일반 결제 성공" });
                  break;
              }
            } else { // 결제 금액 불일치. 위/변조 된 결제
              console.log("위조된 결제 시도");
              //throw { status: "forgery", message: "위조된 결제시도" };
            }
          } catch (e) {
            res.status(400).send(e);
          }
        });

module.exports = router;

 

 

다음주소API를 사용하긴 하지만 개발 도중, 주소 값을 DB에 저장할 필요가 없다고 느껴져서 DB에 주소 값을 저장하지 않았습니다.

(택배 배송 추적을 구현해볼 수 없어서 주소를 저장해야할 필요성을 느끼지 못했습니다.)

 

마이페이지 접근 제한은 미들웨어를 사용하지 않고 FRONTEND에서 메인 페이지로 이동하도록 구현했습니다.

(아래에 코드가 첨부되어 있습니다.)

 

i'mport 결제 API를 사용했고 테스트 결제까지 실행되지만 사업자등록번호가 없어서 보안처리(액세스토큰발급, DB접근)는 실행되지 않습니다.

 

 

 

프론트엔드

 

모든 페이지가 아닌 주요 기능이 포함되어있는 페이지 코드만 있습니다.

또한 가독성을 위해 CSS 코드는 제외했습니다.

 

 

회원가입(다음주소API)

<template>

<div class="signuppage">
    <h1>회원 가입</h1><br><br><br>
    <div class="inputbox">
    <form action="/signup" method="post">
        <h3>ADDRESS</h3>
        <input type="text" id="postcode" name="postcode" placeholder="우편번호">
        <button type="button" @click="search()">우편번호 찾기</button><br>
        <input type="text" id="roadAddress" name = "roadAddress" placeholder="도로명주소">
        <input type="text" id="jibunAddress" name = "jibunAddress" placeholder="지번주소">
        <span id="guide" style="color:#000;display:none"></span>
        <input type="text" id="detailAddress" name="detailAddress" placeholder="상세주소">
        <input type="text" id="extraAddress" name="extraAddress" placeholder="참고항목">
      <h3>ID</h3>
      <input type="text" name="id" pattern="^(?=.*\d)(?=.*[a-z]).{5,15}" placeholder="최소 5자 이상, 숫자 필수, 영문"> <br>
        <h3>PASSWORD</h3>
       <input type="text" name="password" pattern="^(?=.*\d)(?=.*[a-z]).{8,15}" placeholder="최소 8자 이상, 숫자 필수, 영문"> <br>
        <h3>NAME</h3>
       <input type="text" name="name" maxlength="4" pattern="^[가-힣]+$" placeholder="한글만, 최대 4자"> <br>
       <h3>PHONE NUMBER</h3>
       <input type="text" name="phone" maxlength="11" pattern="^(\d{3}).*(\d{3}).*(\d{4})" placeholder="전화번호 입력"> <br>
      <button @click="signup()">회원 가입</button>
    </form>
    </div>
    </div>
</template>


<script>

import axios from 'axios'

export default {
    name : "SignUpPage",

    data (){
        return {
            id : '',
            password : '',
            name : '',
            address : ''

        }
    },

    methods : {
        signup() {
            axios.post( "http://olrang.shop:3000/signup", {
                    id : this.id,
                    password : this.password,
                    name : this.name,
                    // address : this.postcode + this.roadAddress + this.jibunAddress,
                    phone : this.phone,
                }).then((response) => {
                        this.response = response
                    
                }).catch((error) => {  
                    this.error=error  
               
                
            });
        },

        search(){
            new window.daum.Postcode({
            oncomplete: (data) => {
                // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                // 도로명 주소의 노출 규칙에 따라 주소를 표시한다.
                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                var roadAddr = data.roadAddress; // 도로명 주소 변수
                var extraRoadAddr = ''; // 참고 항목 변수

                // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
                    extraRoadAddr += data.bname;
                }
                // 건물명이 있고, 공동주택일 경우 추가한다.
                if(data.buildingName !== '' && data.apartment === 'Y'){
                   extraRoadAddr += (extraRoadAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                }
                // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                if(extraRoadAddr !== ''){
                    extraRoadAddr = ' (' + extraRoadAddr + ')';
                }

                // 우편번호와 주소 정보를 해당 필드에 넣는다.
                document.getElementById('postcode').value = data.zonecode;
                document.getElementById("roadAddress").value = roadAddr;
                document.getElementById("jibunAddress").value = data.jibunAddress;
                
                // 참고항목 문자열이 있을 경우 해당 필드에 넣는다.
                if(roadAddr !== ''){
                    document.getElementById("extraAddress").value = extraRoadAddr;
                } else {
                    document.getElementById("extraAddress").value = '';
                }

                var guideTextBox = document.getElementById("guide");
                // 사용자가 '선택 안함'을 클릭한 경우, 예상 주소라는 표시를 해준다.
                if(data.autoRoadAddress) {
                    var expRoadAddr = data.autoRoadAddress + extraRoadAddr;
                    guideTextBox.innerHTML = '(예상 도로명 주소 : ' + expRoadAddr + ')';
                    guideTextBox.style.display = 'block';

                } else if(data.autoJibunAddress) {
                    var expJibunAddr = data.autoJibunAddress;
                    guideTextBox.innerHTML = '(예상 지번 주소 : ' + expJibunAddr + ')';
                    guideTextBox.style.display = 'block';
                } else {
                    guideTextBox.innerHTML = '';
                    guideTextBox.style.display = 'none';
                }
            }
        }).open();
        }
    }
}


</script>

 

 

상품페이지(i'mport 결제 API)

<template>
  <div class="pastintro">
      <h1>[Best] 원하는 과거로 갈 수 있는 포션</h1><br><br>
      <h3>해당 포션은 14세 이상부터 권장합니다.</h3><br><br><br>
        <img :src="require('@/assets/pastpotion.jpg')">
      <div class="info">
        <h2>사용 대상</h2>
        <h2 v-for="item in items" :key="item">{{item.message}} </h2><br><br><br>
        <h2>사용법</h2>
        <h2 v-for="use in list" :key="use">{{use.message}} </h2><br><br><br>
        <h2>주의 사항</h2>
        <h2 v-for="warn in warns" :key="warn">{{warn.message}} </h2><br><br><br>
        <h3>구매 전, 구매하실 포션의 이름과 사진이 맞는 지 확인해주세요.</h3><br><br><br><br>
        <img :src="require('@/assets/pastpotion.jpg')">
        <div class="purchase">
        <h1>[Best] 원하는 과거로 갈 수 있는 포션</h1><br><br>
        <h1>1500 원</h1>
        <button type="button" @click="past()">구매하기</button>
        </div>
    </div>
  </div>
</template>

<script>

import jQuery from 'jquery'

export default {
    name : "PastIntro",
    data (){
        return {
            items : [
                {message : '과거가 그리운 사람'},
                {message : '어린 시절로 돌아가고 싶은 사람 등'}
                
            ],

            list : [
                {message : '자기 전, 시간 차를 두지 않고 한 번에 끝까지 마시면 됩니다.'},
                {message : '눈을 감고 원하는 과거를 떠올리면 됩니다.'},
                {message : '꿈 속에서 과거에 머무를 수 있는 시간은 실제 수면 시간과 동일합니다.'},
                
            ],

            warns : [
                {message : '최소 2주에 1번만 마시는 것을 권장합니다.'},
                {message : '꿈 속에서의 일은 절대 현재, 미래에 영향을 미치지 않습니다.'}
                

            ]
        }
    },
methods : {
    past(){
        var IMP = window.IMP;
            IMP.init("imp07168267");

            IMP.request_pay({ // param
                pg: "kakao", // PG사 선택
                pay_method: "card", // 지불 수단
                merchant_uid: 'merchant_' + new Date().getTime(), //가맹점에서 구별할 수 있는 고유한id
                name: "[Best] 원하는 과거로 갈 수 있는 포션", // 상품명
                amount: 1500, // 가격
                digital: true, // 실제 물품인지 무형의 상품인지(핸드폰 결제에서 필수 파라미터)
                app_scheme : '' // 돌아올 app scheme
            }, function (rsp) { // callback
                
                if (rsp.success) {
                    jQuery.ajax({
                        url: "http://olrang.shop:3000/aptitude", // 가맹점 서버
                        method: "POST",
                        headers: { "Content-Type": "application/json" },
                        data: {
                            imp_uid: rsp.imp_uid,
                            merchant_uid: rsp.merchant_uid
                            //기타 필요한 데이터가 있으면 추가 전달
                        }
                    }).done(function (data) {
                        // 가맹점 서버 결제 API 성공시 로직
                        switch(data.status){
                        case "success":
                            alert("결제 성공!");
                        break;
               
                        }
                    });

                } else {
                    // 결제 실패 시 로직,
                alert("결제 실패. 에러 내용 : "+rsp.error_msg);
                }
            });
    }
}

}
</script>

 

 

마이페이지(페이지 접근 제한)

<template>
<div class = "mypage">
    <h2>개인정보 수정</h2> <br>
    <h3>기존 번호 : {{mydata}}</h3>
    <form action="/mypage" method="post">
        <input type="text" name="phone" maxlength="11" pattern="^(\d{3}).*(\d{3}).*(\d{4})" placeholder="전화번호"> <br>
    <button @click="update()">수정하기</button>
    </form>
</div>
  
</template>

<script>
import axios from 'axios'

export default {
    name : "MyPage",
    data(){
        return{
            mydata:''
        }
    },
        created() {
            axios.get('http://olrang.shop:3000/api/mypage',  { 
                withCredentials: true,
            }).then((response) => { 
                this.mydata = response.data.phone
                if(this.mydata == undefined)
                    this.$router.replace('/') // 로그인 안되어 있을 경우 마이페이지 접근 제한
                
            }).catch((error) => {  
                this.error=error    
            
                
            })
        },

        method : {
            update(){
            axios.post('http://olrang.shop:3000/mypage',  { 
                withCredentials: true,
                phone : this.phone,
            }).then((response) => { 
                this.response = response
                
            }).catch((error) => {  
                this.error=error    
            })

            }
        }
    }

</script>

 

 

긴 글 읽어주셔서 감사합니다.

 

 

세션 구현 시도

 

https://codelist.tistory.com/62

 

[Vue.js, Node.js] 쿠키로 로그인/로그아웃 구현

여는 글 쿠키는 보안상 좋지 않아서 쿠키만으로는 로그인/로그아웃을 구현하기엔 좋지 않다. 대부분은 세션+쿠키를 사용하는 것 같다. 그래서 나도 처음엔 세션부터 구현하려고 했다. 세션에는

codelist.tistory.com

 

 

이제 DB 삭제가 되기 때문에 내가 세션을 자체적으로 구현하려고 했다.

 

로그인 시 DB 생성 -> DB 생성 시 TTL 사용해서 지정 시간 뒤에 자동으로 세션 값 삭제

 

근데 이틀 동안 TTL에서 막혀서 그냥 라이브러리 써야겠다 싶었는데 1번 글에서 만났던 문제를 또 마주하게 됐다.

 

 

        req.session.userinfo = users.phone
                         ^

TypeError: Cannot read property 'userinfo' of undefined

이런 에러가 나는데 다른 사람들은 잘만 사용하던데 난 왜 안될까...

 

일단 req.session 자체가 undefined라 키도 당연히 오류나는 것 같아서 req.session undefined 구글링부터 해보고 있다...

 

 

 

 

+22.1.4 해결

 

 

req.session undefined를 구글링하고 있는데 아래 포스팅을 보게 됐다.

 

https://jaroinside.tistory.com/15

 

1. express-session

express 사용시 express-session 을 사용하는 경우가 많습니다. 이번에 한 블로그의 공부를 따라하던중, express-session 을 사용하였는데, 로그인 구현 관련이었습니다. 로그인을 하면서 session을 저장하기

jaroinside.tistory.com

 

그래서 순서를 바꿨더니 페이지 접속 시 에러가 생겼다.

 

TypeError: Secret string must be provided.

 

사실 이 에러는 처음 보는 에러가 아니었다.

 

세션 이것저것 해보면서 몇 번 만나봤던 에러인데 나는 분명히 secret을 명시해뒀기 때문에 그냥 이상하고 뜬금없는 에러라고 생각해서 무시했는데 이번에는 저 에러에 대해서 구글링을 해봐야겠다는 생각이 들었다.

 

그리고는 아래 글을 보고 해결할 수 있었다.

 

https://stackoverflow.com/questions/38326191/node-js-session-login

 

Node.js session login

Node.js does not seem to be remembering my session. I am using Express with cookie-parser and express-session as middleware. In my application, I use Mongo to keep usernames and passwords. The

stackoverflow.com

 

cookieParser 설정에 대한 내용인데 먼저 글을 읽어보고 나는 cookieParser에 딱히 설정한 게 없을텐데..? 하면서 내 코드를 봤더니...

 

app.use(cookieParser({sameSite : 'none', secure : true}));

 

secure가 true로 되어있었다.

 

배포하고나서 쿠키 안될 때 건드렸던 부분인데 여기서도 secure가 문제일 줄 몰랐다.

 

그래서 secure를 지웠으나 그래도 똑같은 에러가 생겼는데 에러를 다시보면 secret을 제공해야한다- 라는 내용이므로 아래처럼 작성하니 session이 작동했다.

 

app.use(cookieParser('secret'));

 

 

session이 안되는 건 당연히 session 문제일 줄 알았는데 cookieParser가 문제일 줄 몰랐다.

 

 

기존 코드도 다시 보고 없던 에러가 생기면 유심히 봐야겠다...

 

 

++session store 설정 참고 코드

https://kimjingo.tistory.com/43

 

 

마이페이지-개인정보수정 기능을 구현하고 있다.

 

이전에 DB 값 삭제(탈퇴) 구현에서 한참을 애먹었었다.

 

https://codelist.tistory.com/71

 

[Node.js, Vue.js, MongoDB] ::디버깅 실패:: 세션 구현하기

기록 파일에 세션을 저장하는 것도 여전히 안되고 그나마 DB에 세션을 저장하는 게 될 가능성이 있다고 판단해서 여기를 집중적으로 해봤는데 여전히 안된다. 파일에 세션 저장하는 것처럼 아예

codelist.tistory.com

 

https://codelist.tistory.com/81

 

[Node.js, Vue.js] 탈퇴 기능(mongodb)

나는 mongodb를 사용하기 때문에 remove(구버전이라 이제 사용 X), deleteOne(), deleteMany() 등의 명령어를 사용해서 document를 삭제해야하는 데 삭제가 안됐다. 해결하고보니 두 가지의 문제점이 있었다.

codelist.tistory.com

 

그래도 해결했으니 설령 UPDATE 기능이 안되더라도 대처할 수 있겠다 싶었는데 UPDATE는 웬만한 시도를 다 해봤으나 동작하지 않았다.

 

UPDATE에서 막힐 거라고는 생각도 못했었는데 DELETE보다 안 되니까 이쯤되면 사실 DELETE도 완벽하게 구현된 게 아닌가? 라는 생각이 들었다.

 

일단 update, updateOne, findAndModify, findOneAndUpdate 등등 시도해봤고 앞에 컬렉션이름.~, db이름.~ 등 다 갖다붙여봤고 DELETE 구현할 때 시도해봤던 FindOne/Find 사용해보고 아예 없애보기도 했는데 5일동안 구현하지 못했다.

 

 

해결

 

참고한 곳 : https://velog.io/@yejineee/Mongoose-Atomic-Update-%EB%B0%A9%EC%8B%9D%EC%9D%84-%EC%B0%BE%EC%95%84%EC%84%9C

 

Mongoose Atomic Update 방식을 찾아서

Account 컬렉션은 transaction 도큐먼트의 objectId를 자신의 필드인 transactions에 추가하여야 한다. 그 과정은 다음 두 가지 일을 해야 한다. Account Object Id로 Account 도큐먼트를 찾는다. 그 도큐먼트의 trasa

velog.io

 

 

 

backend/routes/index.js

router.post('/mypage', (req, res) => { //마이페이지 정보 수정
  User.findOne({name : req.cookies.session},(err, users) => { 
      if(!req.cookies.session || err) {  // 쿠키 없거나 에러 났을 때
        res.redirect('/');
      } else {
        users.updateOne({$set : {phone : req.body.phone}}).exec(); // 여기!!
        res.redirect('/mypage');
      }
  }) 
})

 

마지막에 .exec() 붙이니까 된다.