오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀이

✔️ 팀 프로젝트 최종 점검

✔️ 챌린지반 수업


⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀이
10:00 ~ 17:00 - 팀 프로젝트 최종 점검

17:00 ~ 19:00 - SQL 문제 풀기

19:00 ~ 20:00 - 챌린지반 수업


📜 프로그래머스 코테 문제 풀이

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀이

 

둘만의 암호

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/155652.%E2%80%85%EB%91%98%EB%A7%8C%EC%9D%98%E2%80%85%EC%95%94%ED%98%B8

 

AlgorithmCodingTest/프로그래머스/1/155652. 둘만의 암호 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

function solution(s, skip, index) {
    let answer = [];

    for (let i = 0; i < s.length; i++) {
        let c = s[i];
        for (let j = 0; j < index;) {
            c = String.fromCharCode(c.charCodeAt() + 1);

            // 다시 a로 바꿈
            if (c === String.fromCharCode('z'.charCodeAt() + 1)) {
                c = 'a';
            }

            let isSkip = false;

            for (let k = 0; k < skip.length; k++) {
                if (c === skip[k]) {
                    isSkip = true;
                    break;
                }
            }

            if (isSkip === false) {
                j++;
            }            
        }

        answer.push(c);
    }

    return answer.join('');
}

 

📜 팀 프로젝트 최종 점검

10:00 ~ 17:00 - 팀 프로젝트 최종 점검

 

팀 프로젝트를 최종으로 점검하고, ppt를 작성했다.

ppt를 작성하면서 트러블 슈팅에 대해 작성하게 되었는데, 

우리 팀의 트러블 슈팅은 총 2가지였다.

 

첫번째는 다음과 같다.

 

 

api/Rating/Play URL과 api/:target/Play URL을 동시에 사용할 경우,

api/:target/Play  가 상위에 선언이 되어 있고,

api/Rating/Play  가 아래에 선언 되어 있을 때, api/Rating/Play로 접근하려고 하면

 

api/:target/Play로만 접근하게 된다. 이유는 :target에 Rating이라는 값이 들어가기 때문..

따라서 :target을 맨 뒤로 보내 문제를 해결했다.

 

두번째는 node-schedule의 사용 문제였다.

node-schdule로 일정 시간마다 호출해주려고 했으나,

예를 들어 5초마다 반복하려고 구성했는데, 실제로 5초가 될경우에만 호출이 되는 방식으로 스케줄링을 하는 패키지였다.

만약, 2초에 호출하면 7초에 호출이 되어야하는데, 5초, 10초, 5초 에 실행이 되는 방식..

setInterval을 통해 호출하는 방식으로 변경했다.

 

시연영상 링크

https://www.youtube.com/watch?v=GysPHl-4oYc

 

 

 

📜  SQL 문제 풀기

17:00 ~ 19:00 - SQL 문제 풀기

 

챌린지반 숙제인 SQL문제를 풀었다.

마땅한 문제집이 없어서 프로그래머스에 있는 문제중에서 풀었다.

 

-- 루시와 엘라 찾기
SELECT ANIMAL_ID, NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS
WHERE NAME IN ('Lucy','Ella','Pickle','Rogan','Sabrina','Mitty')
ORDER BY ANIMAL_ID;

-- 이름이 없는 동물의 아이디
SELECT ANIMAL_ID
FROM ANIMAL_INS
WHERE NAME IS NULL
ORDER BY ANIMAL_ID

-- 조건에 맞는 회원수 구하기
SELECT COUNT(USER_ID) USERS
FROM USER_INFO
WHERE JOINED LIKE '2021%' AND AGE >= 20 AND AGE <= 29

-- 카테고리 별 상품 개수 구하기
SELECT ANIMAL_TYPE, COUNT(*) AS count
FROM ANIMAL_INS
GROUP BY ANIMAL_TYPE
ORDER BY ANIMAL_TYPE

 

오늘의 목표

더보기

✔️ 팀프로젝트 진행


⏱️ 오늘의 일정

9:00 ~ 21:00 팀프로젝트 진행


📜 팀프로젝트 진행

9:00 ~ 21:00 팀프로젝트 진행

 

팀프로젝트를 진행했다.

저번주 주말에 랭킹 조회를 구현했다.

 

랭킹 전체 조회

rankingRouter.get('/Ranking/AllCheck', async (req, res, next) => {
    // rankingScore 컬럼을 기준으로 내림차순한 전체 데이터 값들을 가져옴
    const currentRankings = await prisma.ranking.findMany({
        orderBy: {
            rankingScore: 'desc'
        }
    });
    
    return res.status(200).json({ ... currentRankings });
});

 

ranking Table에 들어있는 값을 전부 읽어온다.

rankingScore 값을 기준으로 내림차순해서 클라이언트에게 전달한다.

 

 

특정 유저 랭킹 조회

rankingRouter.get('/Ranking/SingleCheck/:userId', async (req, res, next) => {
    const { userId } = req.params;

    const userRanking = await prisma.ranking.findFirst({
        where: {
            userId: +userId
        }
    })

    return res.status(200).json({ userRanking });
});

 

조회하고자 하는 유저의 id값을 받아서 ranking table에 읽어온 후 클라이언트에게 전달한다.

 

DBRankingChangeScore ( 순위 변동 계산 )

export async function DBRankingChangeScore() {    
    const currentRankings = await prisma.ranking.findMany({
        orderBy: {
            rankingScore: 'desc'
        }
    });

    // 순서대로 순회하며 랭킹 설정
    let rank = 1;
    currentRankings.map((currentRanking) => {
        currentRanking.rank = rank;
        rank++;
    });


    // 순위 변동 계산
    for (let pRankKey in previousRankings) {
        const pRankData = previousRankings[pRankKey];
        for (let cRankKey in currentRankings) {
            const cRankData = currentRankings[cRankKey];
            if (pRankData.userId == cRankData.userId) {
                const rankChangeScore = pRankData.rank - cRankData.rank;

                cRankData.rankChangeScore = rankChangeScore;
                break;
            }
        }
    }

    const rankingTableUpdateFinish = async (tx) => {
        // ranking Table에 순위와 순위 변동을 기록
        await Promise.all(
            currentRankings.map(async (currentRanking) => {
                await tx.ranking.update({
                    where: {
                        userId: currentRanking.userId
                    },
                    data: {
                        rank: currentRanking.rank,
                        rankChangeScore: currentRanking.rankChangeScore
                    }
                })
            })
        );
    };
    await executeTransaction(rankingTableUpdateFinish);

    // 현재 시점 순위 변동 저장
    previousRankings = currentRankings;
}

 

이전 ranking table의 값과 DBRankingChangeScore을 호출했을 때의 ranking table의 값을 비교해서 순위변동을 계산하고,

DB에 저장한다.

 

app.js

DBRankingChangeScore();

setInterval(() => {
  DBRankingChangeScore();
}, process.env.RANKING_RANK_CHANGE_TIME);

 

서버가 켜질때, 우선 DBRankingChangeScore()를 호출해서 랭킹 데이터를 최신화 시켜준다.

서버에서 정한 일정 시간마다 DBRankingChangeScore()를 호출해서 순위를 계산해준다.

 

 

프론트엔드는 아래와 같이 구성했다.

로그인 화면

 

회원가입 화면

 

게임 화면

오늘의 목표

더보기

✔️ 팀 프로젝트

 


⏱️ 오늘의 일정

9:00 ~ 21:00 - 팀 프로젝트 


📜 팀 프로젝트

9:00 ~ 21:00 - 팀 프로젝트 

 

팀 프로젝트를 진행했다. 

테스트용으로 만든 회원가입과 로그인 부분을 내가 생각하는 로직으로 바꿧다.

 

우선 로그인 전용으로 인증을 검사하는 미들웨어를 만들었다.

export default async function (req, res, next) {
    const { authorization } = req.headers;
    const { id } = req.body;

    do {
        // AccessToken이 없음
        if (authorization === undefined || authorization.length == 0) {
            req.authLoginState = process.env.LOGIN_AUTH_FAIL_AUTHORIZATION_NOT_FOUND;
        }
        else {
            // AccessToken이 있음
            const [c2sTokenType, c2sAccessToken] = authorization.split(' ');
            if (c2sTokenType !== process.env.TOKEN_TYPE_CHECK) {
                req.authLoginState = process.env.LOGIN_AUTH_FAIL_TOKEN_TYPE_NOT_MATCH;
            }        

            const decodedToken = jwt.decode(c2sAccessToken);            
            if(decodedToken.id !== id)
            {
                req.authLoginState = process.env.LOGIN_AUTH_FAIL_DIFFERENT_ACCESS_TOKEN;
                break;
            }

            // 토큰 검증
            const verifyToken = ValidateToken(c2sAccessToken, process.env.ACCESS_TOKEN_SECRET_KEY);                        
            if (!verifyToken) {
                req.authLoginState = process.env.LOGIN_AUTH_FAIL_TOKEN_EXPIRED;
                break;
            }            

            req.authLoginState = process.env.LOGIN_AUTH_SUCCESS;
        }
    } while (0);

    next();
}

 

위 코드에서는 로그인 시도한 유저가 AccessToken을 가지고 있는지 확인한다.

AccessToken이 없거나 빈 내용을 입력하면 req.authLoginState에 LOGIN_AUTH_FAIL_AUTHORIZATION_NOT_FOUND를 저장한다.

 

AccessToken이 있는데 TokenType이 다를 경우에 LOGIN_AUTH_FAIL_TOKEN_TYPE_NOT_MATCH를 저장한다.

AccessToken이 있는데 다른 유저의 AccessToken을 가지고 왔을 경우에는 LOGIN_AUTH_FAIL_DIFFERENT_ACCESS_TOEKN을 저장한다.

 

AccessToken이 있는데 만료됐거나, 값이 다를 경우네는 LOGIN_AUTH_FAIL_TOKEN_EXPIRED를 저장한다.

 

그 외 경우에는 인증 성공으로 판단해서 LOGIN_AUTH_SUCCESS를 저장한다.

 

이후 메인 Login Router로 진행한다.

 

// 로그인

usersRouter.post('/Sign-In', loginAuthMiddleware, async (req, res, next) => {
    // 아이디, 비밀번호 가져오기
    const { id, password } = req.body;

    // userDB에 아이디가 있는지 확인
    const user = await prisma.users.findFirst({
        where: {
            id: id
        }
    });

    // 아이디 검사
    if (!user) {
        return res.status(404).json({ message: `${id}은 존재하지 않는 아이디 입니다.` });
    }

    // 비밀번호 검사
    if (!(await bcrypt.compare(password, user.password))) {
        return res.status(404).json({ message: `비밀번호가 일치하지 않습니다.` });
    }

    // RefreshTokens 테이블에 RefreshToken이 있는지 확인
    const refreshTokenExist = await prisma.refreshTokens.findFirst({
        where: {
            id: id
        }
    });

    switch (req.authLoginState) {
        case process.env.LOGIN_AUTH_FAIL_AUTHORIZATION_NOT_FOUND:
            // AccessToken이 없음
            FirstLogin(id, refreshTokenExist, res);
            break;
        case process.env.LOGIN_AUTH_FAIL_TOKEN_TYPE_NOT_MATCH:
            // AccessToken을 가지고 있지만 토큰 타입이 일치하지 않음
            return res.status(404).json({ message: '토큰 타입이 일치하지 않습니다.' });
        case process.env.LOGIN_AUTH_FAIL_TOKEN_EXPIRED:
            // AccessToken이 만료됨
            AccessTokenExpired(id, refreshTokenExist, res);
            break;
        case process.env.LOGIN_AUTH_FAIL_DIFFERENT_ACCESS_TOKEN:
            DifferentAccessToken(refreshTokenExist, res);
            break;
        case process.env.LOGIN_AUTH_SUCCESS:
            // 로그인 성공
            return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
    }
})

 

로그인에서는 기본적으로 아이디와 비밀번호를 검사하고,

RefreshToken Table에 RefreshToken이 있는지 찾은 후,

로그인 인증에서 저장한 authLoginState값에 따라 다른 로직으로 처리한다.

 

//------------------------------------------------------
// 처음 로그인
//------------------------------------------------------
async function FirstLogin(id, refreshTokenDBData, res) {
    if (refreshTokenDBData === null) {
        // RefreshToken이 Table에 없음
        // 처음 로그인 대상으로 해석
        // JWT로 AccessToken 생성
        const s2cAccessToken = CreateAccessToken(id);
        // JWT로 RefreshToken 생성
        const s2cRefreshToken = CreateRefreshToken(id);

        // 응답 헤더에 accessToken 기록
        res.header("authorization", s2cAccessToken);

        const nowTime = MakeTime(false);
        const expiredAtTokenTime = MakeTime(false, parseInt(process.env.REFRESH_TOKEN_EXPIRE_TIME_DB));

        // RefreshToken DB에 저장
        const refreshToken = await prisma.refreshTokens.create({
            data: {
                id: id,
                token: s2cRefreshToken,
                createdAt: nowTime,
                expiredAt: expiredAtTokenTime
            }
        });

        return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
    }
    else {        
        const s2cAccessToken = CreateAccessToken(id);
        res.header("authorization", s2cAccessToken);

        return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
    }
}

 

FirstLogin 에서는 로그인 시도한 대상이 처음으로 로그인한 대상인지를 판단한다.

만약 RefreshToken Table에서 RefreshToken을 찾지 못하면, 해당 유저를 처음 로그인한 대상으로 해석하고,

AccessToken과 RefreshToken을 발행하고, Refresh Token을 Table에 저장한다.

 

RefreshToken을 이미 가지고 있는 대상이면 AccessToken을 재발행해준다.

 

async function AccessTokenExpired(id, refreshTokenDBData, res) {
    if (refreshTokenDBData !== null) {
        // AccessToken을 가지고 있고, AccessToken이 만료됨
        // refreshToken을 DB에 가지고 있음

        // AccessToken을 재발급
        // RefreshToken이 만료되었는지 확인
        const refreshTokenCheck = ValidateToken(refreshTokenDBData, process.env.REFRESH_TOKEN_SECRET_KEY);
        if (!refreshTokenCheck) {
            // RefreshDB createdAt, expiredAt 컬럼 사용 용도 : 
            // JWT 없이 Refresh Token의 Expire 시간을 제어할 수 있는 용도로 사용
            // 예) JWT로 3시간 후 만료로 발급했으나 모종의 이유로 1시간 후 만료로 바꿔야하는 경우,
            //     JWT는 수정을 할 수 없기 때문에 DB에 있는 시간값으로 판정한다.

            // 현재 시간과 db에 저장되어 있는 시간 차를 구함
            const dbTimeDifferenceSecond = TimeDifference(refreshTokenDBData.expiredAt, "second");

            // 상수값으로 설정해둔 Refresh Token 만료 시간을 지나면 
            if (dbTimeDifferenceSecond >= parseInt(process.env.REFRESH_TOKEN_EXPIRE_DB_CHECK_TIME)) {
                // RefreshToken을 재발급
                const s2cRefreshToken = CreateRefreshToken(id);

                // 현재 시간을 구함
                const nowTime = MakeTime(false);
                // Refresh Token 만료 시간을 구함
                const expiredTime = MakeTime(false, parseInt(process.env.REFRESH_TOKEN_EXPIRE_TIME_DB));

                // Refresh Token을 업데이트
                await prisma.refreshTokens.update({
                    where: { id: id },
                    data: {
                        token: s2cRefreshToken,
                        createdAt: nowTime,
                        expiredAt: expiredTime
                    }
                });
            }
        }

        const s2cAccessToken = CreateAccessToken(id);
        res.header("authorization", s2cAccessToken);

        return res.status(200).json({ message: `AccessToken이 재발급 되었습니다. ${id}로 로그인에 성공했습니다. ` });
    }
    else {
        // AccessToken을 가지고 있고, AccessToken이 만료됨
        // refreshToken을 DB에 가지고 있지 않음
        // 불량 유저로 판정 ( 서버에 문제가 생겨 refreshToken을 기록 못할경우가 있어서 불량 유저로 판정하기엔 무리)      
        // return res.status(404).json({ message: `비정상 로그인 시도` });

        return res.status(404).json({ message: `refreshToken DB Empty` });
    }
}

 

AccessTokenExpired에서는 AccessToken을 재발행 해준다.

만약 RefreshToken을 DB에 가지고 있지 않을 경우에는 불량유저로 판단해 로그인 시켜주지 않는다.

( 이 부분은 실제 이런경우가 있는지 보고 싶어서 따로 처리한 것이고, 원래는 서버에 문제가 생겨 RefreshToken을 DB에 기록하지 못할 경우도 생길 수 있기 때문에 AccessToken을 재발행 해줘야한다고 튜터님에게 답을 얻었다. )

 

RefreshToken을 DB에 가지고 있으면, RefreshToken이 만료됐는지 JWT로 먼저 확인해주고,

추가로 DB에 있는 시간값을 기준으로 만료됐는지 확인 한 후,

( DB에 있는 시간값을 기준으로 만료됐는지 추가로 확인하는 이유는 다음과 같다. JWT로 발행을하고 나서 모종의 이유로 만료시간을 수정을 해야할 경우, JWT의 만료시간을 수정할 수 없어서 DB값을 기준으로 최종적으로 판단해준다. )

만료됐으면 RefreshToken을 재발행한다.

 

🌙 하루를 마치며

오늘 생각했던 회원가입과 로그인 부분을 구현할 수 있어서 기분이 좋았다.

로그인 부분에서 불량유저를 판단하는 부분이 애매해 튜터님에게 가서 물어보니,

다른 관점으로 대답을 해주셔서 판단에 많은 도움이 되었다.

 

주말에는 추가로 맡은 랭킹 부분을 완료해 월요일에 테스트 할 수 있도록 해야겠다.

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀기

✔️ 팀 프로젝트 ( 회원가입, 로그인 )


⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기
10:00 ~ 11:00 - 스탠다드반 CS 강의

11:00 ~ 21:00 - 팀 프로젝트

19:00 ~ 20:00 - 챌린지반 수업 

 


📜 프로그래머스 코테 문제 풀기

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기

 

대충만든 자판

 

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/160586.%E2%80%85%EB%8C%80%EC%B6%A9%E2%80%85%EB%A7%8C%EB%93%A0%E2%80%85%EC%9E%90%ED%8C%90

 

AlgorithmCodingTest/프로그래머스/1/160586. 대충 만든 자판 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

function solution(keymap, targets) {
    var answer = [];    
    for (let i = 0; i < targets.length; i++) {                
        answer.push(0);
        for (let j = 0; j < targets[i].length; j++) {
            const targetChar = targets[i][j];
            let minBtnClick = 1000;            
            let findChar = false;
            for (let k = 0; k < keymap.length; k++) {
                for (let l = 0; l < keymap[k].length; l++) {
                    if (keymap[k][l] == targetChar) {
                        if (l < minBtnClick) {
                            minBtnClick = l + 1;
                            findChar = true;
                        }
                    }
                }
            }
            if (findChar == false) {
                answer[i] = -1;
                break;
            }
            
            answer[i] += minBtnClick;
        }        
    }    
    return answer;
}

 

문제 설명에 있는 대로 반복문을 사용해 구현했다.

반복문이 많아서 시간초과가 날까 싶었는데, 초과하는 예시는 없었다.

targets 의 개수 만큼 answer에 0을 넣고 해당 값을 증가시켜주는 방식으로 구현했다.

 

 

📜 스탠다드반 CS 강의

9:00 ~ 10:00 - 스탠다드반 CS 강의

 

IP 주소와 클래스풀 주소 체계에 대한 내용과 서브넷 마스크를 배웠다.

스탠다드반 수업내용은 따로 정리해 포스팅하고 링크를 남겨야겠다.

 

 

📜 팀 프로젝트

11:00 ~ 21:00 - 팀 프로젝트

 

본격적인 팀 프로젝트 구현 첫 날을 맞았다.

우선 기초적인 회원가입과 로그인을 구현해 Push 했다.

 

회원가입 api

// 회원가입
usersRouter.post('/Sign-Up', async (req, res, next) => {
    const { name, nickname, id, password, confirmPassword } = req.body;
    const isExistUser = await prisma.users.findFirst({
        where: {
            id: id
        }
    });

    if (isExistUser) {
        return res.status(404).json({ message: `이미 존재하는 아이디입니다.` });
    }

    if (confirmPassword === undefined) {
        return res.status(404).json({ message: `비밀번호 확인을 입력하세요` });
    }

    if (password !== confirmPassword) {
        return res.status(404).json({ message: `비밀번호와 비밀번호 확인이 일치하지 않습니다.` });
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    // 유저 생성해서 Users table에 저장
    const user = await prisma.users.create({
        data: {
            name: name,
            nickname: nickname,
            id: id,
            password: hashedPassword
        }
    });

    // 랭킹 생성해서 Ranking table에 저장
    const rank = await prisma.ranking.create({
        data: {
            userId: user.userId
        }
    });

    // 스쿼드 생성해서 Squad table에 저장
    const squad = await prisma.squad.create({
        data: {
            userId: user.userId
        }
    });

    return res
        .status(201)
        .json({ message: `${id}로 회원가입이 완료되었습니다.` });
});

 

회원가입은 위 코드에서 추가할 부분이 없어보이기는 한다.

아마 닉네임 중복 점검 정도 추가하지 않을까 싶다.

 

 

 

로그인 api

usersRouter.post('/Sign-In', async (req, res, next) => {
    // 아이디, 비밀번호 가져오기
    const { id, password } = req.body;
    // AccessToken이 있는지 확인
    const { authorization } = req.headers;

    // userDB에 아이디가 있는지 확인
    const user = await prisma.users.findFirst({
        where: {
            id: id
        }
    });

    // 아이디 검사
    if (!user) {
        return res.status(404).json({ message: `${id}은 존재하지 않는 아이디 입니다.` });
    }

    // 비밀번호 검사
    if (!(await bcrypt.compare(password, user.password))) {
        return res.status(404).json({ message: `비밀번호가 일치하지 않습니다.` });
    }

    // JWT로 AccessToken 생성
    const s2cAccessToken = CreateAccessToken(id);
    // JWT로 RefreshToken 생성
    const s2cRefreshToken = CreateRefreshToken(id);   

    // 응답 헤더에 accessToken 기록
    res.header("authorization", s2cAccessToken);

    return res.status(200).json({ message: `${id}로 로그인에 성공했습니다.` });
})

 

로그인은 기본적으로 jwt로 accesstoken을 발급하고, 헤더에 저장해 발급해주는 방법을 우선 사용했다.

테스트를 해야하기 때문에 인증은 간단하게!

 

로그인은 RefreshToken을 활용해 로직을 구현하려고 한다.RefreshToken을 DB에 저장해 관리한다.AccessToken이 만료되면, RefreshToken으로 AccessToken의 재발행 여부를 판단한다.

 

 

 

📜 챌린지반 수업

19:00 ~ 20:00 - 챌린지반 수업 

 

오늘 챌린지반 수업에서는 DB Query 중 집계함수와 그룹핑에 대한 내용을 배웠다.

추가로 숙제를 내줬는데 프로그래머스 SQL 문제 4개를 풀어오는 숙제다.

 

랜덤하게 챌린지반 수강생 중 3명을 골라 발표를 시킨다고 공지를 받았다.

SQL 문제를 풀고, TIL에 기록해야겠다.

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀기

✔️ 팀 프로젝트 과제 발제

✔️ 팀 회의


 

⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기
10:00 ~ 11:00 - 팀 프로젝트 과제 발제

11:00 ~ 20:00 - 팀 회의


 

📜 프로그래머스 코테 문제 풀기

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기

 

문자열 나누기

 

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/140108.%E2%80%85%EB%AC%B8%EC%9E%90%EC%97%B4%E2%80%85%EB%82%98%EB%88%84%EA%B8%B0

 

AlgorithmCodingTest/프로그래머스/1/140108. 문자열 나누기 at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

문제를 한번에 이해를 못해서 여러번 읽고 이해한 후,

문제 설명에 맞게 코드를 짜니 쉽게 해결했다.

function solution(s) {
    let answer = 0;
    let isX = 0;
    let notX = 0;
    let x = "";

    for (let i = 0; i < s.length; i++) {
        if (x == "") {
            x = s[i];
            isX++;
            continue;
        }

        if (x == s[i]) {
            isX++;
        }            
        else {
            notX++;
        }            

        if (isX == notX) {
            answer++;
            isX = 0; notX = 0;
            x = "";
        }
    }

    if (isX != 0) {
        answer += 1;
    }        

    return answer;
}

 

 

📜 팀 과제 발제

10:00 ~ 11:00 - 팀 과제 발제

 

총 12일 간의 시간 기한으로 한 팀 과제가 발제되었다.

이번 팀 과제는 풋살 게임의 서버를 만드는 과제다.

 

 

필수 구현 기능은 다음과 같다.

 

1. 회원가입 / 로그인 기능

2. 캐시 구매 기능

3. 선수 데이터 준비

4. 선수 뽑기 기능

5. 나만의 팀 꾸리기 기능

6. 축구 게임 기능

 

도전 기능은 다음과 같다.

 

1. 승리 / 패배 시 게임 점수 조정 기능

2. 유저 랭킹 조회 기능

3. 점수 기반 자동 매치 메이킹 기능

4. 선수 강화 기능

 

제출은 9 / 25일 수 오전 12:00로 정해졌다.

 

 

📜  팀 회의

11:00 ~ 21:00 - 팀 회의

 

S.A를 작성해 21:00 까지 튜터님한테 피드백을 받으라는 공지가 있어서, 팀원들과 함께 S.A를 작성했다.

 

1. 와이어 프레임

 

회원가입 화면

 

로그인 화면

 

인벤토리 확인 및 뽑기 화면

 

대상 지정 게임

 

 

레이팅 게임

 

강화 화면

 

2. API 명세서

 

API 명세서를 팀원들끼리 작성하면서 서로 업무를 분담했다.

내가 맡은건 회원가입과 로그인, 그리고 전체 랭킹 조회, 로그를 맡았다.

 

 

3. 데이터 베이스 관계를 ERD DIAGRAM을 이용해 작성했다.

 

오늘의 목표

더보기

✔️ 스탠다드반 수업

✔️ 개인 과제 마무리

✔️ 데이터 베이스개념, 인증, 인가 특강

✔️ 챌린지반 수업


⏱️ 오늘의 일정

10:00 - 11:00 - 스탠다드 반 수업
11:00 ~ 12:00 - 개인 과제 마무리
 

12:00 ~ 13:00 - 개인 과제 다듬기

13:00 ~ 14:00 - 점심시간

14:00 ~ 16:00 - 데이터 베이스개념, 인증, 인가 특강

16:00 ~ 18:00 - 개인 과제 다듬기

18:00 ~ 19:00 - 저녁시간

19:00 ~ 20:00 - 챌린지반 수업20:00 ~ 21:00 - 팀원 코드 리뷰


📜 스탠다드 반 수업

10:00 - 11:00 - 스탠다드 반 수업

 

오늘 수업은 발제자분들의 발표로만 이루어졌다.

OSI 7계층 중 3계층인 네트워크 계층에 대한 주제로 발표가 시작되었다.

 

 

📜 개인 과제 마무리

11:00 - 12:00 - 개인과제 마무리

 

 

길고 길었던 개인과제를 마무리했다.

개인과제를 마무리하고 미처 구현하지 못했던 기능들도 구현해 추가로 제출했다.

 

아이템 구입할때 발견하지 못했던 부분을 발견해서 버그를 수정했다.

 

 

 

 // 캐릭터의 인벤토리를 찾는다.
 const chracterInventory = await prisma.inventory.findFirst({
     where: {
         characterId: character.characterId
     }
 });
 
 // 인벤토리에서 비어있는 공간을 찾는다.
 const emptyInventoryItem = await prisma.inventoryItem.findFirst({
     where: {
         inventoryId: chracterInventory.inventoryId,
         itemId : 0
     }
 });

 // 비어있는 공간에 아이템을 넣는다.
 await prisma.inventoryItem.update({
     where: {
         inventoryItemIndex: emptyInventoryItem.inventoryItemIndex,
         inventoryId : chracterInventory.inventoryId
     },
     data: {
         itemId: searchItem.itemId,
         inventoryItemCount: count
     }
 })

 

위 코드는 아이템을 구입하고 인벤토리에 넣을 때, 인벤토리에서 빈 공간을 찾은 후 해당 공간에 아이템을 넣는 코드다.

비어있는 공간에 아이템을 넣는 부분의 로직은 다음과 같다.

 

인벤토리 아이템을 업데이트 하는데, 인벤토리 아이템의 위치값이 위에서 찾은 비어있는 공간의 아이템의 위치값과 같고,해당 인벤토리 아이템이 캐릭터가 소유하고 있는 인벤토리일 경우,해당 인벤토리 아이템의 아이템 코드와 개수를 업데이트 한다.

 

이처럼 구성을하고 실행을 하니 에러가 났다.

 

 

에러 내용을 살펴보니 inventoryitemId를 활용하라는 의미의 버그였다.

생각해봐도 왜 굳이 저거를 써야할까..?

로우 쿼리 자체가 문제가 되는가 싶어서 MySQL Workbench에서 위 로직으로 쿼리를 짜서 실행해보니 문제 없이 실행됐다.

 

Prisma 자체의 문제인가 싶어서 튜터님에게 찾아가 문의를 하니, Prisma에서는 PK로 설정한 값을 활용해 where문을 작성해야한다는 말을 들었다.

 // 비어있는 공간을 찾는다.
 const emptyInventoryItem = await prisma.inventoryItem.findFirst({
     where: {
         inventoryId: chracterInventory.inventoryId,
         itemId: 0
     }
 });

// 비어있는 공간에 아이템을 넣는다.
await prisma.inventoryItem.update({
    where: {
        inventoryItemId: emptyInventoryItem.inventoryItemId                    
    },
    data: {
        itemId: searchItem.itemId,
        inventoryItemCount: count
    }
});

 

그래서 위처럼 수정을 했다.

 

인벤토리 아이템을 업데이트 하는데,

인벤토리 아이템의 ID 값이 비어있는 아이템의 ID 값이 같은 대상을 찾고,

해당 인벤토리 아이템의 아이템 코드와 개수를 업데이트한다.

 

이처럼 구성을하고 실행을 하니 깔끔하게 업데이트가 되었다.

위처럼 구성하는 것이 기본적인 방법이라고 튜터님에게서 조언을 받았다.

기본 ID 값을 활용해 업데이트 하는 방식으로 SQL 쿼리를 만들어야겠다.

 

📜 데이터베이스 개념, 인증 인가 특강

14:00 ~ 16:00 - 데이터 베이스개념, 인증, 인가 특강

 

앞서 노드 강의에서 배운 데이터베이스의 개념과 인증, 인가에 정리 차원에서 특강 수업이 있어서 들었다.

 

📜 챌린지반 수업

19:00 - 20:00 - 챌린지 반 수업

 

매주 화 목 진행하는 챌린지반 수업에 참여했다.

이번 강의에서는 RDBMS에 대한 개념을 주제로 해서 수업이 이루어졌다. 

중간 중간 쿼리에 대한 문제를 내주고 푸는 짧은 문제 풀이 시간도 있었다.

 

📜 팀원 코드 리뷰

20:00 - 21:00 - 팀원 코드 리뷰

 

20시 부터 팀원 코드 리뷰 시간이 있어서 참여했다.

다른 분들의 코드를 보면서 의아한 부분은 질문하고, 도움이 될만한 부분을 발견해 많은 도움이 되었다.

 

https://dbdiagram.io/home

 

dbdiagram.io - Database Relationship Diagrams Design Tool

 

dbdiagram.io

 

무료로 DB 다이어그램을 구성할 수 있는 사이트를 공유 받아서 앞으로 자주 이용해야겠다.

 

🌙 하루를 마치며

오늘로 개인과제가 마무리 되었다.

9월 13일 오전 10시에 팀 과제 발제가 있다고 공지를 받았다.

어떤 문제가 나올지 기대되고, 팀원 코드 리뷰를 하면서 팀 과제 분배를 어떻게 할지도 상의했다.

우선 지금 생각으로는 개인과제를 하며 발견한 router 단위로 분배를 하는법

아니면 어떤 큼직한 로직 담당으로 분배를 하는법 2가지 정도를 생각하고 의견을 나눴다.

 

최종으로는 팀 과제 발제를 듣고나서 적절하게 분배해 수행하는 것으로 이야기를 마루리햇다.

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀기

✔️ 개인과제

✔️ Node.js 강의 듣기


⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기
10:00 ~ 13:00 - Node.js 강의 듣기

13:00 ~ 14:00 - 점심시간

14:00 ~ 20:00 - Node.js 강의 듣기

20:00 ~ 21:00 - 개인과제 기본 완료


📜 프로그래머스 코테 문제 풀기

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기

 

숫자 짝꿍 문제를 풀었다. 

 

function solution(X, Y) {
    let newSet = [];
    var answer = '';

    let findIndex = [];    

    for (let i = 0; i < X.length; i++) {        
        for (let j = 0; j < Y.length; j++) {
            if (X[i] === Y[j]) {
                let duplicateIndex = false;
                for (let k = 0; k < findIndex.length; k++) {
                    if (findIndex[k] == j) {
                        duplicateIndex = true;
                        break;
                    }
                }

                if (duplicateIndex == false) {
                    newSet.push(X[i]);
                    findIndex.push(j);
                }                
            }
        }
    }

    let answerArray = Array.from(newSet);
    answerArray.sort((a, b) => b - a);

    if (answerArray.length == 0) {
        return "-1";
    }
    else if (answerArray[0] == 0) {
        return "0";
    }
    else {
        return answerArray.join("");
    }    
}

 

 

📜 Node.js 강의 듣기

10:00 ~ 20:00  - Node.js 강의 듣기

 

드디어 길고 길었던, Node.js 강의를 모두 다 들었다.

한 강의당 시간이 꽤 길고, 블로그에 정리하면서 이해안되는 부분은 구글로 찾고 하느라 시간이 매우 오래걸렸다. ㅠㅠ

이번주 남은 시간 동안 다시 한번 복습해 빠진 부분이 있으면 추가로 글을 작성해야겠다.

 

2024.09.11 - [Javascript] - [Javascript] 로그 ( Log ), 에러 처리 ( Error Handling ) 미들웨어

 

[Javascript] 로그 ( Log ), 에러 처리 ( Error Handling ) 미들웨어

로그 미들웨어 ( Log Middleware )는 클라이언트의 모든 요청 사항을 기록해 서버의 상태를 모니터링하기 위한 미들웨어다. 로그 미들웨어는 클라이언트의 요청을 기록해 어플리케이션을 모니터링

program-yam.tistory.com

 

2024.09.11 - [내일배움캠프/실습] - [Javascript][실습][게시판 프로젝트] 게시글 API

 

[Javascript][실습][게시판 프로젝트] 게시글 API

게시글 생성 API 비즈니스 로직게시글을 작성하려는 클라이언트가 로그인된 사용자인지 검증한다.게시글 생성을 위한 title, content를 body로 전달받는다.Posts 테이블에 게시글을 생성한다. 게시글

program-yam.tistory.com

 

2024.09.11 - [데이터베이스] - [DATABASE] 트랜잭션 ( Transaction )

 

[DATABASE] 트랜잭션 ( Transaction )

트랜잭션 ( Transaction )은 작업의 완전성을 보장해주기 위해 사용되는 개념이다.특정한 작업을 전부 처리하거나, 전부 실패하게 만들어 데이터의 일관성을 보장해주는 기능이다. 트랜잭션 ( Transa

program-yam.tistory.com

 

2024.09.11 - [데이터베이스] - [DATABASE] Prisma Transaction ( [게시판 프로젝트] 회원가입 적용, 사용자 히스토리 테이블 생성해 적용해보기 )

 

[DATABASE] Prisma Transaction ( [게시판 프로젝트] 회원가입 적용, 사용자 히스토리 테이블 생성해 적용

Prisma의 트랜잭션은 여러개의 쿼리를 하나의 트랜잭션으로 수행할 수 있는 Sequential 트랜잭션과 Prisma가 자체적으로 트랜잭션의 성공과 실패를 관리하는 Interactive 트랜잭션이 존재한다. Sequential

program-yam.tistory.com

 

트랜잭션에 대해 다시 한번 개념을 이해하고, 실습을 통해 더욱 더 이해를 하게 되었다.

 

 

📜 개인과제 기본 완료

20:00 ~ 21:00 - 개인과제 기본 완료

 

 

개인 과제 기본을 완료했다. 내일 목요일 ( 9/12 ) 정오까지 제출이긴 한데...

쪽잠을 자더라도 도전 과제를 구현하고 자야겠다.

 

어제 의문점을 가졌던 로그인에서의 AccessToken과 RefreshToken의 사용방법에 대한 질문을 튜터님에게 했다.

로그인 단계에서는 보통 안전하다고 믿고 가는 경우도 있고, 내가 의문을 가졌던 RefreshToken이 변조되었을 경우에 해당 사용자를 거부 할 수도 있고 이처럼 회사마다 방식이 각각 다르다고 전달을 받았다.

 

지금 현재 내코드에서는 무조건 아이디와 비밀번호만 같다면 로그인 성공처리를 해주기는 하는데, 로그인 거부 할 수 있는 방법을 찾아 거부 할 수 있도록 해야겠다.

 

아이템 수정

// 아이템 수정
router.post('/item-edit/:itemCode', async (req, res, next) => {
    const { itemCode } = req.params;
    const editData = req.body;

    // 아이템을 찾음
    const searchItem = await prisma.items.findFirst({
        where: {
            itemCode: +itemCode
        }
    });

    // 못찾으면 에러 반환
    if (!searchItem) {
        return res
            .status(401)
            .json({ message: `수정할 아이템이 서버에 존재하지 않습니다.` });
    }

    // 가격 속성 삭제해서 가격 수정할 수 없게 함
    delete editData.itemPrice;

    // 아이템 나머지 능력치가 정해지지 않을 경우 각 데이터를 0으로 초기화
    if (editData.itemStr === undefined) {
        editData.itemStr = 0;
    }

    if (editData.itemDex === undefined) {
        editData.itemDex = 0;
    }

    if (editData.itemInt === undefined) {
        editData.itemInt = 0;
    }

    // item 테이블 업데이트
    await prisma.items.update({
        where: {
            itemCode: +itemCode
        },
        data: {
            ...editData
        }
    });

    return res
        .status(200)
        .json({ message: `${itemCode} 아이템 수정 성공` });
});

 

아이템 전체 조회

// 아이템 전체 조회
router.get('/item', async (req, res, next) => {
    // itemCode를 기준값으로 해서 오름차순으로 정렬해 데이터를 가져옴
    const allItems = await prisma.items.findMany({
        select: {
            itemCode: true,
            itemName: true,
            itemPrice: true,
            itemStr: true,
            itemDex: true,
            itemInt: true
        },
        orderBy: {
            itemCode: 'asc'
        }
    });

    // 조회한 전체 아이템 반환
    return res.status(200).json({ allItems });
});

 

특정 아이템 조회

// 특정 아이템 조회
router.get('/item/:itemCode', async (req, res, next) => {
    const { itemCode } = req.params;

    // itemCode로 item 테이블에서 아이템 찾음
    const item = await prisma.items.findFirst({
        where: {
            itemCode : +itemCode
        },
        select: {
            itemCode: true,
            itemName: true,
            itemPrice: true,
            itemStr:true,
            itemDex: true,
            itemInt:true,
        }
    })

    // 아이템을 찾지 못하면 에러 반환
    if (!item) {
        return res
            .status(401)
            .json({ message: '조회하고자 하는 아이템을 찾을 수 없습니다.' });
    }

    // 찾은 아이템 정보 반환
    return res
        .status(200)
        .json({ item });
});

 

게시글 생성 API 비즈니스 로직

  1. 게시글을 작성하려는 클라이언트가 로그인된 사용자인지 검증한다.
  2. 게시글 생성을 위한 title, content를 body로 전달받는다.
  3. Posts 테이블에 게시글을 생성한다.

 

게시글은 사용자 ( Users )는 여러개의 게시글 ( Posts )을 등록할 수 있다는 조건에 따라 사용자와 1:N의 관계를 가지고,

현재 로그인 한 사용자의 정보가 존재했을 때만 게시글을 생성할 수 있도록 구현해야 한다.

 

사용자를 생성했을 때와 동일하게, routers/posts.router.js 파일을 생성하고,  app.js 파일에 라우터를 추가해보자.

 

게시글 생성 API

// src/routes/posts.router.js

import express from 'express';
import { prisma } from '../utils/prisma/index.js';
import authMiddleware from '../middlewares/auth.middleware.js';

const router = express.Router();

/** 게시글 생성 API **/
router.post('/posts', authMiddleware, async (req, res, next) => {
  const { userId } = req.user;
  const { title, content } = req.body;

  const post = await prisma.posts.create({
    data: {
      userId: +userId,
      title,
      content,
    },
  });

  return res.status(201).json({ data: post });
});

export default router;

 

사용자 인증 미들웨어를 통해 게시글을 작성하려는 클라이언트가 로그인된 사용자 인지 검증하였고,

전달된 userId 값을 이용해, 사용자와 1:N 관계를 맺고 있는 게시글을 생성하도록 구현했다.

 

PostsRouter를 등록한 app.js

// src/app.js

import express from 'express';
import cookieParser from 'cookie-parser';
import LogMiddleware from './middlewares/log.middleware.js';
import ErrorHandlingMiddleware from './middlewares/error-handling.middleware.js';
import UsersRouter from './routes/users.router.js';
import PostsRouter from './routes/posts.router.js';

const app = express();
const PORT = 3018;

app.use(LogMiddleware);
app.use(express.json());
app.use(cookieParser());
app.use('/api', [UsersRouter, PostsRouter]);
app.use(ErrorHandlingMiddleware);

app.listen(PORT, () => {
  console.log(PORT, '포트로 서버가 열렸어요!');
});

 

게시글 조회 API

게시글 목록 조회 API와 게시글 상세 조회 API는 이전에 prisma-curd에서 구현한 Posts 테이블과 동일한 코드로 구성되어 있다.

 

게시글 목록 조회 API

// src/routes/posts.router.js

/** 게시글 목록 조회 API **/
router.get('/posts', async (req, res, next) => {
  const posts = await prisma.posts.findMany({
    select: {
      postId: true,
      userId: true,
      title: true,
      createdAt: true,
      updatedAt: true,
    },
    orderBy: {
      createdAt: 'desc', // 게시글을 최신순으로 정렬합니다.
    },
  });

  return res.status(200).json({ data: posts });
});

 

 

게시글 상세 조회 API

// src/routes/posts.router.js

/** 게시글 상세 조회 API **/
router.get('/posts/:postId', async (req, res, next) => {
  const { postId } = req.params;
  const post = await prisma.posts.findFirst({
    where: {
      postId: +postId,
    },
    select: {
      postId: true,
      userId: true,
      title: true,
      content: true,
      createdAt: true,
      updatedAt: true,
    },
  });

  return res.status(200).json({ data: post });
});

오늘의 목표

더보기

✔️ 개인 과제 구현 


⏱️ 오늘의 일정

10:00 ~ 11:00 - 스탠다드반 CS 강의

11:00 ~ 19:00 - 개인과제 구현

19:00 ~ 20:30 - 챌린지반 수업


📜 스탠다드 반 CS 강의

10:00 ~ 11:00 - 스탠다드 반 CS 강의

 

매주 화목에 진행하는 스탠다드반 수업 강의가 있어서 참여했다.

오늘 내용은 이더넷, 허브, 스위치, Mac 주소에 관해 배웠다.

 

📜 개인과제 구현

11:00 ~ 19:00 개인과제 구현

 

git Commit 목록

 

오늘은 속도가 좀 붙어서 로그인 기능과 캐릭터 생성, 캐릭터 삭제, 캐릭터 조회, 아이템 생성 까지 구현했다.

 

로그인 기능에서 고민이 있던 부분을 포스팅하고자 한다.

처음에는 로그인 할때 아이디와 비밀번호가 맞으면 AccessToken을 생성해 반환해주는 걸로 우선 구현을 했다.

 

이렇게 구현하다가는 만약 재로그인한 대상이 해당 유저가 아닐 경우에는? 어떻게 해야할까? 라고 생각이 들어서 재로그인할때는 AccessToken을 살펴보기로 하고 구현했다.

 

내가 생각한 로그인 방식은 다음과 같다.

 1. 로그인 요청한 대상이 AccessToken을 가지고 오지 않았으면

  1.1 처음 발급 하는것이므로 AccessToken과 RefreshToken을 발급하고 로그인 성공 처리한다.

 2. 로그인 요청한 대상이 AccessToken을 가지고 오면

  2.1 해당 AccessToken이 유효한지 판단한다.

   2.1.1 AccessToken이 유효하면 로그인 성공 처리한다.

   2.1.2 AccessToken이 유효하지 않으면, RefreshToken을 가지고 있는지 확인한다.

    2.1.2.1 RefreshToken을 가지고 있으면, RefreshToken이 유효한지 판단한다.

     2.1.2.1.1 RefreshToken이 유효하면, AccessToken을 재발급하고 로그인 성공 처리한다.

     2.1.2.1.2 RefreshToken이 유효하지 않으면, RefreshToken과 AccessToken을 재발급하고 로그인 성공 처리한다.

    2.1.2.2 RefreshToken을 가지고 있지않으면, RefreshToken을 발급하고 AccessToken을 재발급하고 로그인 성공처리

 

위처럼 생각하고 구현해보니 어떻게해도 무조건 로그인은 성공처리가 되는것이 아닌가?

 

다시한번 생각하니 로그인 처리에서는 AccessToken과 RefreshToken을 발급 하는것이 목적인것이고 ( 아이디와 비밀번호만 맞으면 ), 클라이언트에서는 AccessToken을 가지고 로그인을 제외한 다른 작업을 할때, '검증' 하는 것을 목적으로 삼으면 되는것이니까.

 

로그인 처리에서는 앞서 언급했듯이 아이디와 비밀번호만 맞으면 로그인 성공처리로 해주면 되겠구나라는 생각이 들어서 그대로 진행했다.

 

로그인 처리를 제외하고는 다른 캐릭터 생성, 캐릭터 삭제 등 인증이 필요한 곳에는 AccessToken으로 검증을 해 해당 값이 유효하지 않으면 바로 에러를 반환하도록 구현했다.

 

위처럼 생각한 것이 맞는지는 확신할 수 없다.

 

https://velog.io/@chuu1019/Access-Token%EA%B3%BC-Refresh-Token%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C

 

🧐 Access Token과 Refresh Token이란 무엇이고 왜 필요할까?

JWT 토큰은 유저의 신원이나 권한을 결정하는 정보를 담고 있는 데이터 조각이다. JWT 토큰은 비밀키로 암호화되어 있기에 비교적 안전하다. 그런데 탈취 당했을 때가 문제다!! 어떻게 위험을 최

velog.io

위 글을 보면 맞는거 같기도 하고.. 애매..

 

내일 과제를 완료하면 따로 튜터님한테 가서 물어보긴 해야겠다.

 

로그인

// 로그인
router.post('/sign-in', async (req, res, next) => {
    const { id, password } = req.body;
    const user = await prisma.users.findFirst({
        where: { id }
    });

    // 아이디와 비밀번호 검사
    if (!user) {
        return res.status(401).json({ message: `${id}은 존재하지 않는 아이디 입니다.` });
    }
    else if (!(await bcrypt.compare(password, user.password))) {
        return res.status(401).json({ message: `비밀번호가 일치하지 않습니다.` });
    }

    // 재로그인할때 필요한 accessToken을 가져옴
    const c2sAccessToken = req.cookies.accessToken;    

    let s2cAccessToken = 0;
    let s2cRefreshToken = 0;

    // accessToken 발급 여부 판단
    let newAccessToken = false;

    if (!c2sAccessToken) // 액세스 토큰이 없음
    {
        // 액세스 토큰 새 발행
        newAccessToken = true;
    }
    else // 액세스 토큰이 있음
    {        
        // 액세스 토큰 유효한지 확인
        // 유효하면 로그인 성공
        const [tokenType, token] = c2sAccessToken.split(' ');

        // tokenType이 맞는지 확인
        if (tokenType !== process.env.TOKEN_TYPE_CHECK) {
            return res.status(404).send('not found');
        }

        // 다른 유저의 AccessToken을 가지고 왔을 경우
        // 현재 로그인한 유저를 대상으로 AccessToken 재발행
        const myToken = CreateAccessToken(id);
        if (myToken !== c2sAccessToken)
        {
            newAccessToken = true;
        }
        else
        {
            // 나의 AccessToken을 가지고 오면 유효한지 확인
            const payload = ValidateToken(token, process.env.ACCESS_TOKEN_SECRET_KEY);
            if (!payload) // 액세스 토큰이 유효하지 않음
            {
                // 액세스 토큰 새 발행
                newAccessToken = true;
            }
        }        
    }

    // 액세스 토큰 새로 발급
    if (newAccessToken) {
        // DB에서 리프레시 토큰을 읽어옴
        const dbRefreshToken = await prisma.refreshTokens.findFirst({
            where: { userId: id },
            select: {
                token: true
            }
        });        

        // DB에 리프레시 토큰이 없음
        if (dbRefreshToken == null)
        {
            // 액세스 토큰과 리프레시 토큰을 새로 발급
            s2cAccessToken = CreateAccessToken(id);
            s2cRefreshToken = CreateRefreshToken(id);

            // 리프레시 토큰을 DB에 저장
            const newDBRefreshToken = await prisma.refreshTokens.create({
                data: {
                    userId: id,
                    token: s2cRefreshToken
                }
            });   

            // 쿠키 전달
            res.cookie('accessToken', s2cAccessToken);
            res.cookie('refreshToken', s2cRefreshToken);
        }
        else 
        {
            // DB에 리프레시 토큰이 있음

            const [tokenType, token] = dbRefreshToken.token.split(' ');

            // tokenType이 맞는지 확인
            if (tokenType !== process.env.TOKEN_TYPE_CHECK) {
                return res.status(404).send('not found');
            }

            // 리프레시 토큰이 유효한지 확인
            const dbRefreshTokenCheck = ValidateToken(token, process.env.REFRESH_TOKEN_SECRET_KEY);
            if (dbRefreshTokenCheck) // 리프레티 토큰이 유효함
            {
                // 액세스 토큰 발급
                s2cAccessToken = CreateAccessToken(id);                

                // 액세스 토큰 전달
                res.cookie('accessToken', s2cAccessToken);
            }
            else // 리프레티 토큰이 유효하지 않음
            {
                // 액세스 토큰과 리프레시 토큰을 새로 발급
                s2cAccessToken = CreateAccessToken(id);
                s2cRefreshToken = CreateRefreshToken(id);

                // 리프레시 토큰을 DB에 업데이트
                const newDBRefreshToken = await prisma.refreshTokens.update({
                    where: { userId: id },
                    data: {                        
                        token: s2cRefreshToken
                    }
                }); 

                // 쿠키 전달
                res.cookie('accessToken', s2cAccessToken);
                res.cookie('refreshToken', s2cRefreshToken);
            }
        }
    }

    return res
        .status(200)
        .json({ message: `${id}로 로그인 성공` });
});

 

 

캐릭터 생성, 캐릭터 삭제, 캐릭터 조회에서는 authMiddleware를 통해 검증하도록 구현했다.

export default async function (req, res, next) {
    const c2sAccessToken = req.cookies.accessToken;
    if (c2sAccessToken === undefined) {
        return res.status(404).json({ message: 'not found' });
    }

    const [tokenType, accessToken] = c2sAccessToken.split(' ');
    if (tokenType !== process.env.TOKEN_TYPE_CHECK) {
        return res.status(401).json({ message: '토큰 타입이 일치하지 않습니다.' });
    }

    // 토큰 검증
    const decodedToken = ValidateToken(accessToken, process.env.ACCESS_TOKEN_SECRET_KEY);
    if (!decodedToken) {
        return res.status(401).json({ message: '토큰이 만료되었습니다. 로그인을 다시 해주세요' })
    }

    const id = decodedToken.id;

    const user = await prisma.users.findFirst({
        where: {
            id : id
        }
    });

    if (!user) {
        return res.status(401).json({message:`${id} 사용자를 찾을수 없습니다.`})
    }

    req.user = user;

    next();    
}

 

로그인한 유저가 AccessToken을 들고오지 않았을 경우, TokenType이 일치하지 않을 경우,

그리고 Token이 만료할 경우 에러를 반환하도록했다.

오늘의 목표

더보기

✔️ 프로그래머스 코테 문제 풀기

✔️ Node.js 강의 듣기


⏱️ 오늘의 일정

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기

10:00 ~ 17:00 - Node.js 강의

17:00 ~ 21:00 - 개인과제

 

 

📜 프로그래머스 코테 문제 풀기

9:00 ~ 10:00 - 프로그래머스 코테 문제 풀기

 

오늘도 알고리즘 코드카타를 진행해 문제를 풀었다.

 

옹알이(2)

https://github.com/YamSaeng/AlgorithmCodingTest/tree/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/1/133499.%E2%80%85%EC%98%B9%EC%95%8C%EC%9D%B4%E2%80%85%EF%BC%882%EF%BC%89

 

AlgorithmCodingTest/프로그래머스/1/133499. 옹알이 (2) at main · YamSaeng/AlgorithmCodingTest

This is an auto push repository for Baekjoon Online Judge created with [BaekjoonHub](https://github.com/BaekjoonHub/BaekjoonHub). - YamSaeng/AlgorithmCodingTest

github.com

 

처음에는 아기가 말할 수 있는 모든 단어를 우선 조합하고 진행하려 했는데, 옳은 방법 같지가 않아서

좀더 고민을 하다가 아래처럼 풀게 되었다.

 

아기가 말할 수 있는 단어가 포함되어 있는지 찾는 방법이다.

뒤에 babyStrDuplicationCheck는 같은 발음이 연속되는 것을 방지한다.

function solution(babbling) {
    let answer = 0;    

    for (let i = 0; i < babbling.length; i++) {

        let isCheck = true;
        let babyStrDuplicationCheck = 0;

        for (let j = 0; j < babbling[i].length; j++) {           

            if (babbling[i].substr(j, 3) == "aya" && babyStrDuplicationCheck != 1) {
                babyStrDuplicationCheck = 1;
                j += 2;
            }
            else if (babbling[i].substr(j, 2) == "ye" && babyStrDuplicationCheck != 2) {
                babyStrDuplicationCheck = 2;
                j += 1;
            }
            else if (babbling[i].substr(j, 3) == "woo" && babyStrDuplicationCheck != 3) {
                babyStrDuplicationCheck = 3;
                j += 2;
            }
            else if (babbling[i].substr(j, 2) == "ma" && babyStrDuplicationCheck != 4) {
                babyStrDuplicationCheck = 4;
                j += 1;
            }
            else {
                isCheck = false;
                break;
            }            
        }

        if (isCheck == true) {
            answer++;
        }
    }

    return answer;
}

 

 

📜 Node.js 강의

10:00 ~ 17:00 - Node.js 강의

 

2024.09.09 - [IT] - [IT] 인증, 인가

 

[IT] 인증, 인가

인증 ( Authentication ) 인증 ( Authentication )은 서비스를 이용하려는 사용자가 인증된 신분을 가진 사람이 맞는지 검증하는 작업을 뜻한다.인증 ( Authentication )은 일반적인 사이트의 로그인 기능에 해

program-yam.tistory.com

 

 

2024.09.09 - [Javascript/실습] - [Javascript][실습][게시판 프로젝트] 로그인, 회원가입 API

 

[Javascript][실습][게시판 프로젝트] 로그인, 회원가입 API

Express.js를 이용한 게시판 프로젝트를 만들기 전에, 우선 app.js에 기본적인 토대를 만들고 간단한 API를 구현해보자. 모든 Router에서 Prisma를 사용할 수 있도록 utils/prisma/index.js 파일을 생성한다. ap

program-yam.tistory.com

 

2024.09.09 - [IT] - [IT] Access Token, Refresh Token

 

[IT] Access Token, Refresh Token

Access Token Access Token은 사용자의 인증( 예) 로그인 )이 완료된 후 해당 사용자를 인증하는 용도로 발급하는 토큰을 말한다.앞서 쿠키 ( Cookie )에 jwt를 설정하고, 지정된 만료 시간이 지나면 인증이

program-yam.tistory.com

 

2024.09.09 - [Javascript/실습] - [Javascript][실습] Access Token, Refresh Token 발급

 

[Javascript][실습] Access Token, Refresh Token 발급

Access Token과 Refresh Token을 발급해보자. # yarn을 이용해 프로젝트를 초기화합니다.yarn init -y# express, jsonwebtoken, cookie-parser 패키지를 설치합니다.yarn add express jsonwebtoken cookie-parserpackage.json에 "type":"mod

program-yam.tistory.com

 

2024.09.09 - [Javascript] - [Javascript] .env ( 환경 변수 읽어 오기 )

 

[Javascript] .env ( 환경 변수 읽어 오기 )

.env 파일에 설정한 환경변수 값을 읽어 오고 싶을 경우 dotenv 패키지를 설치해 읽어오면 편하다. yarn add dotenv 위 명령어를 이용해 dotenv 패키지를 설치한다. .envACCESS_TOKEN_SECRET_KEY="YamSaeng52"REFRESH_T

program-yam.tistory.com

 

인증, 인가에 대한 개념을 배우고 이를 토대로 실습으로 로그인과 회원가입 API를 작성했다.

Access Token과 Refresh Token을 배우고 이를 토대로 실습으로 직접 Access Token, Refresh Token을 발급했다.

 

📜 개인과제

17:00 ~ 21:00 - 개인과제

 

개인과제를 진행했다. 오늘은 회원가입 까지만 우선 완료하고 내일 캐릭터 생성 까지 끝낼 예정이다.

 

회원 가입 router

router.post('/sign-up', async (req, res, next) => {
    const { id, password, confirmPassword } = req.body;
    const isExistUser = await prisma.users.findFirst({
        where: {
            id,
        },
    });

    const engNumIdRule = /^(?=[a-za-z])(?=.*[0-9]).{2,10}$/;
    if (!engNumIdRule.test(id)) {
        return res.status(409).json({ message: '소문자 영어와 숫자를 조합해 입력하세요 ( 최소 2글자, 최대 10글자 )' });
    }

    if (confirmPassword === undefined) {
        return res.status(409).json({ message: '비밀번호 확인을 입력하세요' });
    }

    if (password.length < 6) {
        return res.status(409).json({ message: '비밀번호는 최소 6자 이상이 되어야 합니다.' });
    }

    if (password != confirmPassword) {
        return res.status(409).json({ message: '비밀번호와 비밀번호 확인이 일치하지 않습니다.' });
    }

    if (isExistUser) {
        return res.status(409).json({ message: '이미 존재하는 아이디입니다.' });
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    const user = await prisma.users.create({
        data: {
            id,
            password: hashedPassword,
        },
    });

    return res.status(201).json({ message: `${id}로 회원가입이 완료되었습니다.` });
});

 

예전에 배운 정규표현식을 이용해 유저한테 입력받은 아이디를 판별햇다.

영어 소문자 + 숫자 조합으로 2자 이상 10자 이하 만 아이디로 등록할 수 있도록 했다.

 

2024.09.10 - [IT] - [IT] 정규표현식

 

[IT] 정규표현식

정규표현식은 어떤 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 사용하는 형식 언어다. 아래 표를 통해 기본적인 정규표현식의 문법을 확인할 수 있다.메타문자기능설명.문자1개의 문

program-yam.tistory.com

 

이번에 찾은 김에 글로 정리했다.

+ Recent posts