-
Notifications
You must be signed in to change notification settings - Fork 0
feat: cli 세팅 및 깃허브 로그인 테스트 #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c875200
556ed54
c8bfa19
ec997cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import os | ||
|
|
||
| from backend.adapters.outbound.dynamodb_repository import DynamoDBRepository | ||
| from backend.application.ports import MemoUseCase | ||
| from backend.application.service import MemoService | ||
|
|
||
|
|
||
| def get_memo_use_case() -> MemoUseCase: | ||
| table_name = os.getenv("DYNAMODB_TABLE") | ||
| if not table_name: | ||
| raise ValueError("DYNAMODB_TABLE 환경 변수가 설정되지 않았습니다.") | ||
|
|
||
| repo = DynamoDBRepository(table_name=table_name) | ||
| return MemoService(repo) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| import argparse | ||
|
|
||
| from cli.application.exceptions import ( | ||
| AuthDeviceFlowError, | ||
| AuthenticationError, | ||
| ) | ||
| from cli.dependencies import container | ||
|
|
||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser(description="mylog CLI", add_help=False) | ||
| parser.add_argument("-h", "--help", action="store_true", help="도움말 출력") | ||
|
|
||
| subparsers = parser.add_subparsers(dest="command") | ||
|
|
||
| subparsers.add_parser("login", help="깃허브 계정으로 로그인합니다.") | ||
| subparsers.add_parser("logout", help="로그아웃합니다.") | ||
|
|
||
| add_parser = subparsers.add_parser("add", help="새로운 메모를 기록합니다.") | ||
| add_parser.add_argument("content", type=str, help="저장할 메모 내용") | ||
| add_parser.add_argument( | ||
| "-c", "--category", type=str, default=None, help="카테고리 지정" | ||
| ) | ||
|
|
||
| list_parser = subparsers.add_parser("list", help="저장된 메모를 조회합니다.") | ||
| list_parser.add_argument( | ||
| "category", type=str, nargs="?", default=None, help="조회할 카테고리" | ||
| ) | ||
| list_parser.add_argument( | ||
| "-l", "--limit", type=int, default=5, help="출력 개수 지정" | ||
| ) | ||
|
|
||
| update_parser = subparsers.add_parser("update", help="기존 메모를 수정합니다.") | ||
| update_parser.add_argument("id", type=str, help="수정할 메모 고유 ID") | ||
| update_parser.add_argument("content", type=str, help="새로운 내용") | ||
|
|
||
| rm_parser = subparsers.add_parser("rm", help="메모를 삭제합니다.") | ||
| rm_parser.add_argument("id", type=str, help="삭제할 메모 고유 ID") | ||
|
|
||
| subparsers.add_parser("help", help="도움말 매뉴얼을 출력합니다.") | ||
|
|
||
| args, unknown = parser.parse_known_args() | ||
|
|
||
| if args.help or args.command == "help" or args.command is None: | ||
| print("""Usage (사용 예시): | ||
|
|
||
| mylog [options] | ||
|
|
||
| Commands: | ||
| login 깃허브 계정으로 로그인하여 클라우드 환경을 연동합니다. | ||
| add 새로운 메모를 기록합니다. (-c 카테고리 지정) | ||
| list 저장된 메모를 조회합니다. (-l 개수) | ||
| update 기존 메모의 내용을 수정합니다. (ID 필요) | ||
| rm 저장된 메모를 삭제합니다. (ID 필요) | ||
| help 현재 화면의 도움말 매뉴얼을 출력합니다.""") | ||
| return | ||
|
|
||
| if args.command == "login": | ||
| try: | ||
| print("GitHub 서버에 로그인 요청 코드를 생성하는 중...") | ||
| login_data = container.auth_service.request_login() | ||
|
|
||
| print("\n" + "=" * 60) | ||
| print("[GitHub 로그인 안내]") | ||
| print("1. 웹 브라우저를 열고 다음 주소로 접속하세요.") | ||
| print(f" {login_data['verification_uri']}") | ||
| print("2. 아래 코드를 입력하고 승인을 완료하세요.") | ||
| print(f" [ {login_data['user_code']} ]") | ||
| print("=" * 60 + "\n") | ||
| print("사용자의 브라우저 로그인을 기다리는 중입니다...") | ||
|
|
||
| if container.auth_service.complete_login( | ||
| login_data["device_code"], login_data["interval"] | ||
| ): | ||
| print("\n[로그인 성공]") | ||
| print("깃허브 토큰이 안전하게 암호화 되어 저장되었습니다.") | ||
| else: | ||
| print("\n[로그인 실패] 깃허브 토큰을 획득하지 못했습니다.") | ||
| except AuthDeviceFlowError as e: | ||
| print(f"\n[로그인 실패] {e}") | ||
| except Exception as e: | ||
| print(f"로그인 예외 발생: {e}") | ||
|
|
||
| elif args.command == "logout": | ||
| try: | ||
| if container.auth_service.logout(): | ||
| print("토큰이 안전하게 파기되었습니다. (로그아웃 성공)") | ||
| else: | ||
| print("이미 로그아웃된 상태거나 삭제할 토큰이 없습니다.") | ||
| except Exception as e: | ||
| print(f"로그아웃 실패: {e}") | ||
|
|
||
| elif args.command == "add": | ||
| try: | ||
| container.memo_service.save_memo( | ||
| content=args.content, | ||
| category=args.category, | ||
| ) | ||
| cat_title = args.category if args.category else "basic" | ||
| print(f"\n> [{cat_title}] 메모가 저장되었습니다.") | ||
| except AuthenticationError as e: | ||
| print(f"\n[인증 실패] {e}") | ||
| except Exception as e: | ||
| print(f"\n[저장 실패] {e}") | ||
|
|
||
| elif args.command == "list": | ||
| try: | ||
| memos = container.memo_service.get_memos( | ||
| category=args.category, | ||
| limit=args.limit, | ||
| ) | ||
|
|
||
| cat_title = args.category if args.category else "전체" | ||
| print(f"\n> [{cat_title}] 최근 메모 ({len(memos)}개 출력)") | ||
| for m in memos: | ||
| print( | ||
| f" [ID: {m.get('id', 'N/A')}] " | ||
| f"{m.get('created_at', '')} | " | ||
| f"{m.get('content', '')}" | ||
| ) | ||
| except AuthenticationError as e: | ||
| print(f"\n[인증 실패] {e}") | ||
| except Exception as e: | ||
| print(f"\n[조회 실패] {e}") | ||
|
|
||
| elif args.command == "update": | ||
| try: | ||
| container.memo_service.update_memo( | ||
| memo_id=args.id, | ||
| content=args.content, | ||
| ) | ||
| print(f"\n> [{args.id}] 메모가 성공적으로 수정되었습니다.") | ||
| except AuthenticationError as e: | ||
| print(f"\n[인증 실패] {e}") | ||
| except Exception as e: | ||
| print(f"\n[수정 실패] {e}") | ||
|
|
||
| elif args.command == "rm": | ||
| try: | ||
| container.memo_service.delete_memo(memo_id=args.id) | ||
| print(f"\n> {args.id}번 메모가 깔끔하게 삭제되었습니다.") | ||
| except AuthenticationError as e: | ||
| print(f"\n[인증 실패] {e}") | ||
| except Exception as e: | ||
| print(f"\n[삭제 실패] {e}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||
| import time | ||||||
|
|
||||||
| import requests | ||||||
|
|
||||||
| from cli.application.ports.outbound import AuthRepositoryPort | ||||||
|
|
||||||
|
|
||||||
| class GitHubDeviceAuthAdapter(AuthRepositoryPort): | ||||||
| DEVICE_CODE_URL = "https://github.com/login/device/code" | ||||||
| ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token" | ||||||
|
|
||||||
| def __init__(self, client_id: str): | ||||||
| self.client_id = client_id | ||||||
|
|
||||||
| def request_device_code(self) -> dict: | ||||||
| headers = {"Accept": "application/json"} | ||||||
| payload = { | ||||||
| "client_id": self.client_id, | ||||||
| "scope": "read:user", | ||||||
| } | ||||||
| response = requests.post( | ||||||
| self.DEVICE_CODE_URL, json=payload, headers=headers, timeout=5 | ||||||
| ) | ||||||
| response.raise_for_status() | ||||||
| return response.json() | ||||||
|
|
||||||
| def poll_for_token(self, device_code: str, interval: int = 5) -> dict: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
default value 왜케 좋아해 ..
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5에서 3으로 바꿔야하면 저 디폴트 값을 바꿔야할지? 착각하기 딱 좋음. 디폴트 밸류는 언제써야할까? 다 쓰면 좋은걸까? 언제쓰는것이 적절한 케이스 인건지? |
||||||
| headers = {"Accept": "application/json"} | ||||||
| payload = { | ||||||
| "client_id": self.client_id, | ||||||
| "device_code": device_code, | ||||||
| "grant_type": "urn:ietf:params:oauth:grant-type:device_code", | ||||||
| } | ||||||
|
|
||||||
| while True: | ||||||
| time.sleep(interval) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 근데 처음에 바로 5초 쉬어야함? |
||||||
| response = requests.post( | ||||||
| self.ACCESS_TOKEN_URL, json=payload, headers=headers, timeout=5 | ||||||
| ) | ||||||
| data = response.json() | ||||||
|
|
||||||
| if "access_token" in data: | ||||||
| return data | ||||||
|
|
||||||
| error = data.get("error") | ||||||
| if error == "authorization_pending": | ||||||
| continue | ||||||
| elif error == "slow_down": | ||||||
| interval += 5 | ||||||
| else: | ||||||
| raise RuntimeError(f"Github 인증 실패: {error}") | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.