문제 상황

  • 프로젝트를 진행하면서 첫 로그인 시 이용약관 동의를 받아야 하는 상황이 생겼다.
  • 학교 관련서비스로 학교 로그인 api를 사용하기 때문에 학번을 id로 사용한다 . 그래서 학번을 기준으로 첫 로그인을 판단해야한다.
  • 현재 프로젝트가 백엔드리스 프로젝트로 존재했기 때문에 “어떻게 첫 로그인을 감지할까?”라는 문제상황이 있었다.

해결 방법

  • 현재 배포를 firebase를 github ci/cd와 붙여서 사용중인데, firestore를 사용해서 학번을 저장하는 방법으로 첫 로그인을 감지하자
  • firestore의 경우에는 콜렉션, 문서, 필드로 이루어진 noSql 형식의 db이다.

(첫 로그인 시)

  1. 로그인 시 firestore의 users 콜렉션을 확인하여 문서에 해당하는 학번이 존재하는지 확인한다.
  2. 존재하지 않으면 첫 로그인 이므로 약관 동의 페이지로 리다이렉션한다.
  3. 약관을 읽고 동의 버튼을 누르는 경우 firestore의 users 콜렉션에 학번을 기준으로 문서를 생성한다.

(두번 째 부터)

  1. 로그인 시 firestore의 users콜렉션에 학번이 존재하는 경우 메인페이지로 리다이렉션한다.

1. firestore생성

2. next.js 프로젝트와 연결

3. firestore의 문서 조회 및 생성으로 첫 로그인을 감지해 약관동의 페이지 리다이렉션 로직 구축

1. firebase의 firestore 추가 및 sdk 값 얻기

  • firebase에 firestore database를 해당하는 프로젝트 이름으로 추가한다.

  • 그 후 firebase에 웹앱을 추가하고 sdk값을 받는다.

2. 프로젝트에 firebase 추가

  • 프로젝트에 firebase를 추가해준다.
# npm 사용 시
npm install firebase
# yarn 사용 시
yarn add firebase

# typescript 사용 시 의존성 부여
npm install --save-dev @types/firebase
yarn add --save-dev @types/firebase
  • 위에서 얻었던 sdk값을 넣은 firebase.js(ts)파일을 루트 디렉토리에 만들어준다.
// 프로젝트 디렉토리의 root에
// firebase.js or firebase.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';

const firebaseConfig = {
  apiKey: '',
  authDomain: '',
  projectId: '',
  storageBucket: '',
  messagingSenderId: '',
  appId: '',
  measurementId: '',
};

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

export { db };

⇒ 이제 firestore과 연결이 완료되어서 사용할 수 있다.

3. 로그인 시 약관동의 페이지로 리다이렉션하기

  • 우선 id값을 받아, 그 id가 firestore의 users 컬렉션 내부에 존재하는 문서값인지 확인하는 함수를 작성한다.
import { doc, getDoc, setDoc } from 'firebase/firestore';
import { db } from '../../../firebase';

export const checkStudentExists = async (
  studentId: string,
): Promise<boolean> => {
  const studentRef = doc(db, 'users', studentId);
  const docSnap = await getDoc(studentRef);
  return docSnap.exists();
};
  • 그런 후 기존의 로그인 로직에서 로그인 성공 시 위에서 작성한 함수를 사용해 약관동의를 받아야할 경우 약관동의 페이지로, 그렇지 않으면 메인페이지로 라우팅을 해준다.
  const handleLogin = async (id: string, password: string) => {
    try {
        .
        .
        . // 로그인 처리하는 로직들

            // firestore의 id값이 존재하는지 확인
      const studentExists = await checkStudentExists(id);
      if (studentExists === false) {
        router.replace('/tos'); // 존재하지 않을 경우 약관동의 페이지로 이동
      } else {
        router.replace('/'); // 존재할 경우 메인페이지로 이동
      }
      return data;
    } catch (err) {

    }

약관동의 시 firestore에 새로운 문서 생성

  • irestore의 users 콜렉션 아래에 id값으로 문서 생성하는 함수 작성
import { doc, getDoc, setDoc } from 'firebase/firestore';
import { db } from '../../../firebase';

export async function createStudent(
  studentId: string,
  userData: Record<string, boolean>,
): Promise<void> {
  const studentRef = doc(db, 'users', studentId);
  try {
    await setDoc(studentRef, userData, { merge: true });
  } catch (error) {
    console.error('Error writing document: ', error);
  }
}
  • 약관동의 버튼 클릭 시 위의 함수 사용해서 문서 id값, 그리고 내부 데이터를 {”isCheckTos”:true}로 새로운 문저 생성
const handleAgree = () => {
    const user = getUserInfo();
    createStudent(user.sId, { isCheckTos: true });
    router.push('/');
  };

⇒ 이렇게 될 경우 이제 최초 로그인시에만 약관동의 페이지가 떠오르고, 약관 동의를 한 후에는 다시 로그인한다해도 바로 메인페이지로 넘어가게 된다.

결과

1. 첫 로그인 시

2. 첫 로그인이 아닐 때 (약관동의 후)

디스코드 웹훅 설정

  • 우선 디스코드의 서버 설정에 들어가서 앱 → 연동 부분을 클릭 한다.

  • 연동 부분에 들어가면 “웹후크” 부분을 클릭한다.

  • 새로운 웹후크를 생성하고 이름을 정해준 후 해당 알림을 받을 채널을 설정한다.→ 그런 후 “웹후크 URL 복사” 버튼을 클릭해서 URL을 복사한다.

깃허브 프로젝트 설정

  • 우선 알림을 받을 repository의 setting 란으로 들어간다.

  • 그러고 나서 code and automation에서 Webhooks 탭을 클릭한다.

  • add webhook을 클릭 한다.

  • 아까 디스코드에서 복사한 webhook 주소를 Payload URL을 붙여 넣은뒤 “/github”라는 문자열을 추가한다.
[디스코드에서 복사한 webhook]/github

  • 그러고 나서 Which events would you like to trgger this webhook? 란에서 어떤 행동들을 알림을 줄지 선택할 수 있다.

  • 이렇게 PR만 추가할 수 있다.

결론

  • 제대로 추가했다면 알림이 잘 오게 된다.

 

'기타' 카테고리의 다른 글

소프트웨어 마에스트로 13기 합격후기  (1) 2023.10.01

문제

 

27440번: 1로 만들기 3

첫째 줄에 1보다 크거나 같고, 1018보다 작거나 같은 정수 N이 주어진다.

www.acmicpc.net

코드

from collections import deque
def bfs(n):
    q = deque([(n, 0)])
    visited = set([n]) 
    while q:
        cur, count = q.popleft()
        if cur == 1:
            return count
        if cur % 3 == 0 and (cur // 3) not in visited:
            q.append((cur // 3, count + 1))
            visited.add(cur // 3)
        if cur % 2 == 0 and (cur // 2) not in visited:
            q.append((cur // 2, count + 1))
            visited.add(cur // 2)
        if (cur - 1) not in visited:
            q.append((cur - 1, count + 1))
            visited.add(cur - 1)
    return -1  
n=int(input())
print(bfs(n))

풀이

  1. n을 입력받는다.
  2. 입력받은 n을 bfs함수에 넣고 돌려준다. 문제에서 주어진 조건을 만족하면 q에 넣는다.
    • X가 3으로 나누어 떨어지면, 3으로 나눈다.
    • X가 2로 나누어 떨어지면, 2로 나눈다.
    • 1을 뺀다.
  3. 단 n의 크기가 최대 10^18로 엄청 크다. 그러므로 최대한 중복된 값을 탐색하지 않기 위해서 visited라는 set()을 선언한다.
  4. 매번 얻는 값들을 visited에 넣어주고, bfs 시행 시 조건을 만족해도 visited에 있는 수들은 q에 넣지 않는다.(중복을 피한다)
  5. 1이 되면 횟수를 return한다.

'알고리즘' 카테고리의 다른 글

[백준] 2096 내려가기 python  (0) 2024.03.19
[백준] 9456 스티커 python  (2) 2024.03.18
[백준] 15663 N과M (9) python  (0) 2024.03.18
[프로그래머스] 여행경로 python  (0) 2024.03.12
[백준] 1063 킹 python  (3) 2024.03.06

문제

 

 

2096번: 내려가기

첫째 줄에 N(1 ≤ N ≤ 100,000)이 주어진다. 다음 N개의 줄에는 숫자가 세 개씩 주어진다. 숫자는 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 중의 하나가 된다.

www.acmicpc.net

코드

import sys
import copy
input = sys.stdin.readline
n= int(input())
# dp배열을 쓰고 싶지만 메모리가 부족하여 현재층과 바로 위층에 사용한 배열만 초기화
bq=[[0,0],[0,0],[0,0]]
cq=[[0,0],[0,0],[0,0]]
for i in range(n):
    arr=list(map(int,input().split()))
    #최대값 구하기
    cq[0][0]=max(bq[0][0],bq[1][0])+arr[0]
    cq[1][0]=max(bq[0][0],bq[1][0],bq[2][0])+arr[1]
    cq[2][0]=max(bq[1][0],bq[2][0])+arr[2]

		#최소값 구하기
    cq[0][1]=min(bq[0][1],bq[1][1])+arr[0]
    cq[1][1]=min(bq[0][1],bq[1][1],bq[2][1])+arr[1]
    cq[2][1]=min(bq[1][1],bq[2][1])+arr[2]
    bq=copy.deepcopy(cq)
resMax=0
resMin=100000000
for i in range(3):
    resMax=max(resMax,max(bq[i]))
    resMin=min(resMin,min(bq[i]))
print(resMax,resMin)

풀이

  • 간단한(하지만 조건있는) dp 문제
  • 메모리가 굉장히 작으므로, 전체를 저장하는게 아닌 사용하는 부분을 계속 업데이트 하는 방식으로 푼다.
  1. 높이 n을 입력받는다.
    • dp 문제이지만 여기서 메모리 크기가 4mb이다. 그러므로 층을 한층씩 내려가면서 본다.
    • 각 층에서 내려갈 수 있는 부분은 인접해 있는 부분이기 때문에 거꾸로 밑층에서도 인접한 곳에서만 올 수 있다.
    • 그러므로 dp[i][0] (왼쪽) : dp[i-1][0] (왼쪽 위), dp[i-1][1] (가운데 위) 중 하나 dp[i][1] (가운데쪽) : dp[i-1][0] (왼쪽 위), dp[i-1][1] (가운데 위), dp[i-1][2] (오른쪽 위) 중 하나 dp[i][2] (오른쪽) : dp[i-1][1] (가운데 위), dp[i-1][2] (오른쪽 위) 중 하나 이다.
    • 최소값과 최대값을 둘다 구하므로, 각각의 위치에 최소,최대를 둘다 구해서 표시해준다.
    • 현재 층(cq)과 바로 위층(bq)만을 사용하고, 다음 층으로 넘어가면 bq=cq로 업데이트 해준다.n만큼 한층씩 내려가면서 최대값과 최솟값을 기록하면서 내려간다.

2. 반복문을 전부 돌고, 1층에서의 최대값과 최소값을 찾아서 출력한다.

특이사항

  • dp의 점화식은 대놓고 문제에서 그림으로 주지만, 메모리가 굉장히 제한적이여서 쉽지 않은 문제였다.
  • dp배열을 cq와 bq로 바꿨는데도 메모리 초과→ 입력받는 arr도 한줄씩 받게 변경 → 시간초과가 나서 sys로 입력속도 빠르게함 → 성공

'알고리즘' 카테고리의 다른 글

[백준] 27440 1로 만들기 3 python  (0) 2024.03.19
[백준] 9456 스티커 python  (2) 2024.03.18
[백준] 15663 N과M (9) python  (0) 2024.03.18
[프로그래머스] 여행경로 python  (0) 2024.03.12
[백준] 1063 킹 python  (3) 2024.03.06

문제

 

9465번: 스티커

첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스의 첫째 줄에는 n (1 ≤ n ≤ 100,000)이 주어진다. 다음 두 줄에는 n개의 정수가 주어지며, 각 정수는 그 위치에 해당하는 스티커의

www.acmicpc.net

코드

T = int(input())
for _ in range(T):
    N = int(input())
    arr = [list(map(int,input().split()))]
    arr.append(list(map(int,input().split())))
    res=[[0 for _ in range(N+1)] for _ in range(2)]
    for i in range(N):
        if(i==0):
            res[0][i+1]=arr[0][i]
            res[1][i+1]=arr[1][i]
            continue
        res[0][i+1]=max(res[1][i]+arr[0][i],res[1][i-1]+arr[0][i])
        res[1][i+1]=max(res[0][i]+arr[1][i],res[0][i-1]+arr[1][i])
    print(max(max(res[0]),max(res[1])))

풀이

  1. 시행횟수 T와 스티커사진의 길이 N, 그리고 각 스티커의 점수를 입력 받는다.
  2. 입력 받은 스티커 사진을 조건에 맞게 찢어서 최대 점수를 얻는 방법을 구한다. (res배열은 해당 위치 최고 점수의 합)1 2
    1 2  
        3
    1. 우선 첫번째 위치의 경우에는 온전히 res배열에 그 스티커의 점수를 저장한다.
    2. 두번째부터 점화식을 세워서 구해준다.
    3. 위의 표처럼 이런 상황에서 3의 최대값을 구하려면 우선 (2까지의 점수합 + 3의 점수)이 있다. 그리고 또한 스티커를 뗀 부분의 상하좌우를 못쓰게 되므로 만약 1까지의 점수합이 더 크다면 (1까지의 점수합 + 3의 점수) 의 경우가 존재한다. 즉 max((2까지의 점수합 + 3의 점수), (1까지의 점수합 + 3의 점수))을 구하게 된다면 해당 위치의 스티커의 최대점수를 얻을 수 있다.
    4. 끝까지 최대점수를 구한뒤 max()함수를 사용하여 최대값을 구한 후 출력해준다.

'알고리즘' 카테고리의 다른 글

[백준] 27440 1로 만들기 3 python  (0) 2024.03.19
[백준] 2096 내려가기 python  (0) 2024.03.19
[백준] 15663 N과M (9) python  (0) 2024.03.18
[프로그래머스] 여행경로 python  (0) 2024.03.12
[백준] 1063 킹 python  (3) 2024.03.06

문제

 

15663번: N과 M (9)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해

www.acmicpc.net

코드

N,M = map(int,input().split())
check = [0 for _ in range(N)]
arr = list(map(int,input().split()))
res=set([])
arr.sort()

def solve(cur,count):
    if count==M:
        res.add(tuple(cur))
    for i in range(N):
        if(check[i]!=0):
            continue
        cur.append(arr[i])
        check[i]=1
        solve(cur,count+1)
        check[i]=0
        cur.pop()

solve([],0)
res=list(res)
a=len(res[0])
res.sort(key=lambda x : tuple(x[i] for i in range(a)))
for i in res:
    tmp = tuple(str(j) for j in i)
    print(str(" ").join(tmp)

풀이

  1. N,M을 입력받고 N크기의 배열 arr을 입력받는다.
  2. check 배열을 만들어주고, arr을 오름차순으로 정렬해준다.
  3. solve함수를 호출한다.
    1. solve함수에서 M의 크기를 만들기 위해서 check로 중복된 값을 제외하고 solve를 재귀호출 해준다.
    2. 만약 count==M이면 원하는 크기이므로 res배열에 추가해준다. 단, arr에 중복된 수가 있어서 결과가 중복될 수 있으므로 res는 set자료형을 사용해서 중복을 방지한다. set 자료형은 변경가능한 list를 넣지 못하므로 tuple로 바꿔서 넣어준다.
  4. solve함수를 다 수행한 후 결과값을 list로 다시 만들어주고 정렬을 해준다.
    1. 정렬을 해줄 때 M의 개수에 따라 정렬 기준이 달라지므로 lambda를 사용할 때 M의 크기만큼 앞에서부터 차례대로 정렬기준을 잡아준다.
  5. 정렬을 완료했으면 출력한다.

'알고리즘' 카테고리의 다른 글

[백준] 2096 내려가기 python  (0) 2024.03.19
[백준] 9456 스티커 python  (2) 2024.03.18
[프로그래머스] 여행경로 python  (0) 2024.03.12
[백준] 1063 킹 python  (3) 2024.03.06
[백준] 25709 1 빼기 python  (0) 2024.02.29

문제

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

코드

def solution(tickets):
    tickets.sort(reverse=True)
    routes = {}
    
    for start,end in tickets:
        if start in routes:
            routes[start].append(end)
        else:
            routes[start] = [end]
    stack = ["ICN"]
    path = []
    
    while stack:
        top = stack[-1]
        if top in routes and routes[top]:
            stack.append(routes[top].pop())  
        else:
            path.append(stack.pop())  
    
    return path[::-1]  
    

풀이

  • 주어진 비행기표를 다 사용한 후 여행경로를 출력해라
  1. 알파벳 순서가 빠른것을 먼저 출력하라고 했기 때문에 tickets을 역순으로 정렬
  2. 우선 routes라는 딕셔너리를 만들어 출발지는 key로 목적지는 value로 만들어준다.
  3. stack 으로 사용할 배열에 제일 첫번째에 “ICN”을 넣어주고, while문을 돌린다.
  4. while문을 돌리면서 갈 경로가 있으면 스택에 추가해주고, 아니면 stack에서 꺼내 path에 추가한다
  5. 마지막 역순으로 되어있는 경로를 뒤집어서 출력해준다.

'알고리즘' 카테고리의 다른 글

[백준] 9456 스티커 python  (2) 2024.03.18
[백준] 15663 N과M (9) python  (0) 2024.03.18
[백준] 1063 킹 python  (3) 2024.03.06
[백준] 25709 1 빼기 python  (0) 2024.02.29
[백준] 1535 안녕 python  (1) 2024.02.28

문제

 

1063번: 킹

8*8크기의 체스판에 왕이 하나 있다. 킹의 현재 위치가 주어진다. 체스판에서 말의 위치는 다음과 같이 주어진다. 알파벳 하나와 숫자 하나로 이루어져 있는데, 알파벳은 열을 상징하고, 숫자는

www.acmicpc.net

코드

a={'A':0,'B':1,'C':2,'D':3,'E':4,'F':5,'G':6,'H':7}
b = {v: k for k, v in a.items()}
d = {'R':(0,1),'L':(0,-1),'B':(-1,0),'T':(1,0),'RT':(1,1),'LT':(1,-1),'RB':(-1,1),'LB':(-1,-1),}

king, stone,n=input().split()
n=int(n)

king_loc = [int(king[1])-1,a[king[0]]]
stone_loc = [int(stone[1])-1,a[stone[0]]]

for _ in range(n):
    move=input()
    i,j = d[move]
    if 0<=king_loc[0]+i<8 and 0<=king_loc[1]+j<8:
        if(king_loc[0]+i==stone_loc[0] and king_loc[1]+j==stone_loc[1]):
            if(0<=stone_loc[0]+i<8 and 0<=stone_loc[1]+j<8):
                stone_loc[0]+=i
                stone_loc[1]+=j
                king_loc[0]+=i
                king_loc[1]+=j
        else:
            king_loc[0]+=i
            king_loc[1]+=j
            
print(b[king_loc[1]]+str(king_loc[0]+1))  
print(b[stone_loc[1]]+str(stone_loc[0]+1))

풀이

  • 체스판에서 돌과 킹의 위치를 움직인 후 위치를 출력해라
  1. 킹과 돌 그리고 움직이는 횟수 n을 입력 받는다.
  2. 입력 받은 좌표값을 정수로 바꿔주고 움직임을 입력받으며 움직인다.
    • 킹의 다음 움직임이 체스판 안에 있고, 그곳이 돌의 위치가 아닐경우
    • 킹의 다음 움직임이 체스판 안에 있고, 그곳이 돌의 위치이고, 돌을 킹과 같은 방향으로 움직였을 때 체스판 안에 있는 경우
    이렇게 두가지만 체크해서 움직인다.
  3. 움직이는 경우는
  4. 다 움직인 후 정수를 다시 체스판 위치 문자열로 바꾸서 출력한다.

특이사항

  • 처음에는 직접 체스판을 구현하려고 했지만, 단순히 좌표만 구하는 거면 굳이 안그래도 되겠다고 생각

+ Recent posts