개인적으로 GitHub 프로필을 꾸미는 것을 좋아합니다.
거기다 커밋도 1,606일째 매일 하는 중이라, GitHub에 한 활동을 기반으로 꾸미는 것 역시 좋아합니다.
이런 선호를 기반으로, GitHub 프로필을 꾸미기 위한 새로운 애플리케이션 gitgarden을 제작해 봤습니다.
기능 소개
위와 같이, GitHub에서 활동을 할 때마다 정원이 가꿔지는 형태의 애플리케이션입니다.
집 등의 오브젝트는, 사용자마다 다른 좌표로 배치됩니다
흔히 '잔디'라 불리는 contributions가 밭에 작물로 자랍니다
새로운 repository를 생성하면, 새로운 나무가 심어집니다
외에도 이슈 생성 / PR 생성 등의 활동에 따라 다양한 풀을 집 근처에 심을 수 있습니다
사용법
GitHub 프로필에 적용하기
위 Markdown에서 YOUR_GITHUB_USERNAME을 자신의 GitHub 아이디로 변경하고 GitHub 프로필 README에 붙여 넣으면 적용은 끝입니다.
옵션
만약 특정 년도의 정원을 보고자 한다면, 위와 같이 year 옵션을 추가하시면 됩니다.
Self-Hosting 가이드
본인의 토큰을 사용하거나, 더 빠른 속도 등을 위해 직접 호스팅하시고 싶다면, ghcr.io/marshallku/gitgarden 이미지를 사용하셔서 호스팅하실 수 있습니다.
HOST: 호스트 주소, 0.0.0.0으로 설정해 주시면 됩니다
PORT: 포트 번호, 기본값은 18085입니다
GITHUB_TOKEN: GitHub API를 사용하기 위한 토큰입니다
CARGO_MANIFEST_DIR: Rust 애플리케이션에서 사용하는 환경 변수입니다
위 값들을 설정해 컨테이너를 실행하시면 됩니다.
혹은 위 환경 변수들을 .env 파일에 저장하고, 아래와 같이 docker-compose.yml 파일을 작성하셔서 실행하실 수도 있습니다.
Technical Implementation
기술 스택
언제나처럼 Rust와 axum으로 제작한 애플리케이션입니다.
최근 Rust로 잡다한 api들을 만들면서, 매번 쓰는 패턴들이 비슷비슷해 http_server_template 프로젝트를 만들었는데, 해당 템플릿으로 만든 첫 프로젝트입니다.
위와 같은 구조로 프로젝트를 구성했습니다.
굳이 controller, service 같이 나눌 필요 없이 코드를 작성할 순 있지만, 아무래도 코드가 길어지다보니 당장 제가 보기도 불편해져서 쪼갰습니다.
GitHub API 연동
GraphQL API
먼저, 위와 같이 GitHub GraphQL API를 호출하는 함수를 작성했습니다.
대부분 요청이 GraphQL로만 이뤄지다 보니, 이 함수 하나만 잘 써두면 대부분 정보들은 가져올 수 있습니다.
다음으론, API가 응답하는 공통적인 형태를 정의해두었습니다.
여담으로, 매크로로 rename까지 가능한게 상당히 편리합니다.
다음으로 별도 파일에 GraphQL 쿼리를 작성해두면,
위와 같이 간단하게 GitHub API를 호출할 수 있습니다.
Contributions
Contribution 영역은, 아무래도 GitHub이랑 생김새를 맞추려면, 레벨이 변화하는 정책부터 신경써야할 게 많습니다.
그러던 와중, contributions 페이지를 발견했습니다.
별도로 쓰일 페이지가 아닌 지라 들어가보면 화면에 보이는 것도 별로 없고, 레이아웃이 왕창 틀어져있는 이상한 페이지를 발견할 수 있는데, html을 열어보면 GitHub 프로필 페이지에 있는 contribution 영역이 그대로 들어가 있습니다.
여기서 각 일자별로 data-date, data-level 두 값만 일단 가져와 hash map에 넣어두면, 일자별 커밋 레벨을 파악할 수 있습니다.
사실 데이터 가져와서 예쁘게 뿌리는 게 전부인 프로젝트라, 여기까지 성공하고 잘 마무리할 수 있겠다고 생각했습니다.
SVG 렌더링
좌표 설정
오브젝트들 배치하면서 꽤 중요하게 생각했던 게 일관성입니다.
무작정 랜덤으로 배치하면 화면을 새로 그릴 때마다 다른 정원이 나오니, 1년동안 같은 정원을 볼 수 있도록 만들고 싶었습니다.
그러던 와중 가장 간단하게 생각한게, 사용자 이름은 사용자마다 고유한 값이니, 이를 해싱한 뒤 시드로 사용하는 것이었습니다.
거기다 추가로, 집 같이 커다란 오브젝트에 나무가 겹치는 불상사를 막기 위해 dead zone을 설정할 수 있도록 했습니다.
오브젝트 렌더링
집, 밭, 꽃, 풀 등 다양한 오브젝트들을 렌더링하기 위해, Renderable trait를 정의했습니다.
각 오브젝트들은 이 trait을 구현하도록 하고, 최종적으로 render 함수들만 호출하면 SVG로 렌더링된 결과를 얻을 수 있습니다.
가장 간단한 예시로 집을 렌더링하는 코드입니다.
위와 같은 형태로 오브젝트들을 분리해두니, format 만으로 충분히 알아볼 수 있을 만큼의 코드를 작성할 수 있어, 굳이 handlebars 등의 템플릿 엔진도 사용하지 않을 수 있었습니다.
렌더링 최적화
GitHub에 이미지를 추가할 땐 링크를 활용할 수 없어, 이미지를 base64로 인코딩해 SVG에 넣어주어야 합니다.
이러면 밭만 렌더링하는 데 최대 365 * 2개의 base64 이미지를 추가해야 하는데, 이러면 별 활동을 안 해도 SVG가 수십 메가바이트가 넘어가는 문제가 발생합니다.
이를 해결하기 위해 제일 상단 Farm 구조체에서, 미리 base64로 인코딩된 이미지들을 SVG에 등록해두고, 필요할 때마다 해당 이미지를 참조하도록 했습니다.
이렇게 Objects enum을 하나 만들어두면, 해당 오브젝트의 이미지가 있는 경로, id, 크기 등을 실수 없이 관리할 수 있습니다.
열매 색상 변경
16*16 크기의 이미지라 잘 보이진 않지만...위와 같이 커밋을 제일 많이 한 상황에는 열매가 맺히도록 했습니다.
여기에 사용자마다 차별점을 더 주기 위해 가장 많이 사용한 언어의 색상이 열매에 반영되도록 작업해봤습니다.
먼저 Illustrator로 열매 이미지를 연 뒤, 한땀한땀 열매 영역만 선택해 위와 같이 오버레이를 제작했습니다.
그러면 위와 같은 svg 파일이 생성됩니다.
사실 이걸 그대로 mask에 적용하면 될 줄 알았는데, 도무지 적용이 되질 않아 한참을 씨름했습니다.
이런데서 발목을 잡힐 줄은 몰랐어서 꽤나 스트레스였습니다.
알고보니 절대적인 경로를 사용해, SVG에 정확히 해당 좌표에만 mask가 적용되고 있었던 터라, 1*1 크기의 mask로 변경하여 해결했습니다.
계획
커밋하는 시간대를 분석하는 작업은 끝내뒀는데, 이걸 정원에 녹일 방법을 모르겠어서 적용을 못했습니다.
하늘을 추가해, star 수에 따라 별을 렌더링하고, 커밋하는 시간대에 따라 하늘 색상을 변경해보면 어떨까 생각 중입니다.
또한, PR, 이슈 등의 활동에 따라 늘어나는 풀들도 너무 규칙 없이 증식만 하는 느낌이라, 좀 더 정돈해보면 어떨까 싶습니다.
그리고 별로 중요한 지표는 아니지만, 현재는 test coverage가 76.2%인데, 그래도 80% 이상으로는 올려보고 싶은 마음입니다.
기여하기
어떠한 형태의 기여도 환영입니다!!
버그 리포트나 기능 추가 뿐 아니라, 상술한 디자인 같은 아이디어도 환영합니다.
로컬 개발 환경 설정
.env.example을 복사해 .env 파일을 생성한 뒤, YOUR_GITHUB_TOKEN을 본인의 GitHub 토큰으로 변경해주세요.
그 다음, cargo run 명령어로 애플리케이션을 실행해 주시면 됩니다.
마무리
오랜만에 꽤 재미있게 작업한 프로젝트입니다.
한 3년쯤 전부터 GitHub에 한 활동을 기반으로 정원을 꾸미는 프로젝트를 진행하고 싶었는데, 드디어 그 꿈을 이뤘네요.
당장에 큰 쓸모는 없을 수 있어도, 개인적으로 프로필도 좀 더 예뻐졌고, 업무에 필요한 간단한 api를 제작할 때도 큰 고민 없이 일단 Rust로 작업할 만큼 이 언어가 편해지기도 했습니다.
GitHub을 단순히 코드 저장소로 사용하는 것을 넘어, 개개인의 성장과 노력을 시각적으로 표현하고자 하는 목표를 갖고 있습니다.
그래서 언젠가는 좀 더 다듬어서 개개인이 커스터마이징할 수 있도록 게임으로 만들어보고 싶기도 합니다.