├── backend-old ├── .gitattributes ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.yml │ │ │ └── application-local.yml │ │ └── java │ │ │ └── dev │ │ │ └── amitwani │ │ │ └── githubwrapped │ │ │ ├── dto │ │ │ ├── graphql │ │ │ │ ├── GraphQLRequest.java │ │ │ │ ├── GitHubContributionStats.java │ │ │ │ ├── GitHubPinnedItems.java │ │ │ │ └── GitHubRepositoryStats.java │ │ │ ├── ResponseDTO.java │ │ │ ├── AllUserDTO.java │ │ │ ├── StatsDTO.java │ │ │ └── TopUserDTO.java │ │ │ ├── GithubWrappedApplication.java │ │ │ ├── repository │ │ │ ├── GitHubStatsRepository.java │ │ │ └── GitHubUserRepository.java │ │ │ ├── controller │ │ │ ├── HealthController.java │ │ │ └── StatsController.java │ │ │ ├── model │ │ │ ├── GitHubUser.java │ │ │ └── GitHubStats.java │ │ │ ├── auth │ │ │ └── AuthFilter.java │ │ │ └── service │ │ │ ├── StatsService.java │ │ │ └── GitHubService.java │ └── test │ │ └── java │ │ └── dev │ │ └── amitwani │ │ └── githubwrapped │ │ └── GithubWrappedApplicationTests.java ├── deployments │ └── application.yml ├── Dockerfile ├── .gitignore ├── .dockerignore ├── fly.toml ├── .mvn │ └── wrapper │ │ └── maven-wrapper.properties ├── pom.xml ├── mvnw.cmd └── mvnw ├── frontend ├── .eslintrc.json ├── app │ ├── favicon.ico │ ├── fonts │ │ └── GeistMonoVF.woff │ ├── [username] │ │ ├── layout.tsx │ │ └── loading.tsx │ ├── actions │ │ ├── all-user-action.ts │ │ ├── top-user-action.ts │ │ └── stats-action.ts │ ├── about │ │ ├── layout.tsx │ │ └── page.tsx │ ├── api │ │ ├── stats │ │ │ ├── all │ │ │ │ └── route.ts │ │ │ ├── top │ │ │ │ └── route.ts │ │ │ └── [username] │ │ │ │ └── route.ts │ │ ├── ai │ │ │ └── route.ts │ │ └── [username] │ │ │ └── og │ │ │ └── route.js │ ├── sitemap.ts │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── public │ ├── robots.txt │ └── github-wrapped-og.png ├── lib │ ├── constants.ts │ ├── utils.ts │ ├── mongodb.ts │ ├── umami.ts │ ├── models │ │ ├── User.ts │ │ └── Stats.ts │ ├── services │ │ └── stats-service.ts │ └── github.ts ├── postcss.config.mjs ├── types │ ├── topUser.ts │ ├── ai.ts │ └── stats.ts ├── components.json ├── next.config.mjs ├── .gitignore ├── components │ ├── donate-button.tsx │ ├── umami-identify.tsx │ ├── ui │ │ ├── input.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── toaster.tsx │ │ ├── wavy-background.tsx │ │ └── chart.tsx │ ├── navbar.tsx │ ├── footer.tsx │ ├── social-share.tsx │ ├── save-image.tsx │ ├── ai-analysis.tsx │ ├── profile-header.tsx │ └── contribution-breakdown.tsx ├── tsconfig.json ├── package.json ├── README.md └── tailwind.config.ts ├── LICENSE └── README.md /backend-old/.gitattributes: -------------------------------------------------------------------------------- 1 | /mvnw text eol=lf 2 | *.cmd text eol=crlf 3 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtwn105/GitHubWrapped/HEAD/frontend/app/favicon.ico -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Allow: / 3 | 4 | Sitemap: https://githubwrapped.xyz/sitemap.xml -------------------------------------------------------------------------------- /frontend/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtwn105/GitHubWrapped/HEAD/frontend/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /frontend/public/github-wrapped-og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtwn105/GitHubWrapped/HEAD/frontend/public/github-wrapped-og.png -------------------------------------------------------------------------------- /backend-old/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9009 3 | 4 | spring: 5 | application: 6 | name: GitHub-Wrapped 7 | 8 | -------------------------------------------------------------------------------- /frontend/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const YEAR = process.env.NEXT_PUBLIC_YEAR || "2025"; 2 | export const YEAR_START = `${YEAR}-01-01T00:00:00Z`; 3 | export const YEAR_END = `${YEAR}-12-31T23:59:59Z`; -------------------------------------------------------------------------------- /frontend/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /frontend/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /frontend/app/[username]/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "@/components/navbar"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return ( 5 | <> 6 | 7 | {children} 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/app/[username]/loading.tsx: -------------------------------------------------------------------------------- 1 | export default function Loading() { 2 | return ( 3 |
4 |
Loading...
5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /backend-old/src/test/java/dev/amitwani/githubwrapped/GithubWrappedApplicationTests.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class GithubWrappedApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/graphql/GraphQLRequest.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto.graphql; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class GraphQLRequest { 11 | private String query; 12 | } -------------------------------------------------------------------------------- /backend-old/deployments/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9009 3 | 4 | spring: 5 | application: 6 | name: GitHub-Wrapped 7 | data: 8 | mongodb: 9 | uri: ${MONGODB_URI} #Z1GoJiJCWOs95qO6 10 | 11 | auth: 12 | token: ${AUTH_TOKEN} 13 | 14 | github: 15 | graphql: 16 | url: ${GITHUB_GRAPHQL_URL} 17 | username: ${GITHUB_USERNAME} 18 | token: ${GITHUB_TOKEN} -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/ResponseDTO.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class ResponseDTO { 11 | private String message; 12 | private Object data; 13 | } 14 | -------------------------------------------------------------------------------- /backend-old/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9009 3 | 4 | spring: 5 | application: 6 | name: GitHub-Wrapped 7 | data: 8 | mongodb: 9 | uri: mongodb://localhost:27017/githubwrapped 10 | 11 | auth: 12 | token: AUTH_TOKEN 13 | 14 | github: 15 | graphql: 16 | url: https://api.github.com/graphql 17 | username: GITHUB_USERNAME 18 | token: GITHUB_TOKEN -------------------------------------------------------------------------------- /frontend/types/topUser.ts: -------------------------------------------------------------------------------- 1 | export interface TopUserResponse { 2 | message: string 3 | data: TopUser[] 4 | } 5 | 6 | export interface TopUser { 7 | username: string 8 | name: string 9 | avatarUrl: string 10 | totalContributions: number 11 | totalCommits: number 12 | totalIssuesClosed: number 13 | totalPullRequestsClosed: number 14 | totalStars: number 15 | totalForks: number 16 | } 17 | -------------------------------------------------------------------------------- /frontend/app/actions/all-user-action.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { getAllUsers as getAllUsersService } from "@/lib/services/stats-service"; 4 | 5 | export const getAllUsers = async () => { 6 | try { 7 | const allUsers = await getAllUsersService(); 8 | return { message: "All users fetched successfully", data: allUsers }; 9 | } catch (error) { 10 | console.error("Error fetching all users:", error); 11 | return { error: "Error fetching all users" }; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/app/actions/top-user-action.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { getTopUsers as getTopUsersService } from "@/lib/services/stats-service"; 4 | 5 | export const getTopUsers = async () => { 6 | try { 7 | const topUsers = await getTopUsersService(); 8 | return { message: "Top users fetched successfully", data: topUsers }; 9 | } catch (error) { 10 | console.error("Error fetching top users:", error); 11 | return { error: "Error fetching top users" }; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/AllUserDTO.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class AllUserDTO implements Serializable { 14 | @Serial 15 | private static final long serialVersionUID = 1L; 16 | private String username; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/app/about/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { YEAR } from "@/lib/constants"; 3 | 4 | export const metadata: Metadata = { 5 | metadataBase: new URL("https://githubwrapped.xyz"), 6 | title: "About GitHub Wrapped", 7 | description: `Your Year in Code ${YEAR} - View your GitHub contributions, stats, and coding journey for ${YEAR}.`, 8 | }; 9 | 10 | export default function AboutLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | return
{children}
; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/GithubWrappedApplication.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | 7 | @SpringBootApplication 8 | @EnableCaching 9 | public class GithubWrappedApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(GithubWrappedApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: "https", 7 | hostname: "avatars.githubusercontent.com", 8 | pathname: "**", 9 | }, 10 | { 11 | protocol: "https", 12 | hostname: "api.producthunt.com", 13 | pathname: "**", 14 | }, 15 | ], 16 | }, 17 | experimental: { 18 | serverComponentsExternalPackages: ["mongoose"], 19 | }, 20 | }; 21 | 22 | export default nextConfig; 23 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /backend-old/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use Eclipse Temurin as the base image for Java 21 2 | FROM eclipse-temurin:21-jre-alpine 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app 6 | 7 | # Copy the application JAR file into the container 8 | COPY target/githubwrapped-0.0.1-SNAPSHOT.jar app.jar 9 | 10 | # Copy the application configuration file into the container 11 | COPY deployments/application.yml application.yml 12 | 13 | # Expose the port that the application will run on 14 | EXPOSE 9009 15 | 16 | # Run the application 17 | ENTRYPOINT ["java", "-jar", "app.jar"] -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/StatsDTO.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto; 2 | 3 | import dev.amitwani.githubwrapped.model.GitHubStats; 4 | import dev.amitwani.githubwrapped.model.GitHubUser; 5 | import lombok.Data; 6 | 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | 10 | @Data 11 | public class StatsDTO implements Serializable { 12 | 13 | @Serial 14 | private static final long serialVersionUID = 1L; 15 | 16 | private String username; 17 | private GitHubUser user; 18 | private GitHubStats stats; 19 | } 20 | -------------------------------------------------------------------------------- /backend-old/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | !**/src/main/resources/application.yml 36 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/repository/GitHubStatsRepository.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.repository; 2 | 3 | import dev.amitwani.githubwrapped.model.GitHubStats; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface GitHubStatsRepository extends MongoRepository { 11 | List findByUsername(String username); 12 | 13 | // Find top 6 users by commits 14 | List findTop6ByOrderByTotalCommitsDesc(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /frontend/components/donate-button.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function DonateButton() { 4 | return ( 5 | 12 | ❤️ Support 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /frontend/components/umami-identify.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { identifyUser } from "@/lib/umami"; 5 | 6 | interface UmamiIdentifyProps { 7 | profileId: string; 8 | firstName?: string; 9 | properties?: Record; 10 | } 11 | 12 | export function UmamiIdentify({ 13 | profileId, 14 | firstName, 15 | properties, 16 | }: UmamiIdentifyProps) { 17 | useEffect(() => { 18 | // Identify user session with Umami using Distinct ID 19 | identifyUser(profileId, { 20 | name: firstName, 21 | ...properties, 22 | }); 23 | }, [profileId, firstName, properties]); 24 | 25 | return null; 26 | } 27 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/TopUserDTO.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serial; 6 | import java.io.Serializable; 7 | 8 | @Data 9 | public class TopUserDTO implements Serializable { 10 | 11 | @Serial 12 | private static final long serialVersionUID = 1L; 13 | 14 | private String username; 15 | private String name; 16 | private String avatarUrl; 17 | private long totalContributions; 18 | private long totalCommits; 19 | private long totalIssuesClosed; 20 | private long totalPullRequestsClosed; 21 | private long totalStars; 22 | private long totalForks; 23 | } 24 | -------------------------------------------------------------------------------- /backend-old/.dockerignore: -------------------------------------------------------------------------------- 1 | # flyctl launch added from .gitignore 2 | !**\.mvn\wrapper\maven-wrapper.jar 3 | 4 | ### STS ### 5 | **\.apt_generated 6 | **\.classpath 7 | **\.factorypath 8 | **\.project 9 | **\.settings 10 | **\.springBeans 11 | **\.sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | **\.idea 15 | **\*.iws 16 | **\*.iml 17 | **\*.ipr 18 | 19 | ### NetBeans ### 20 | nbproject\private 21 | nbbuild 22 | dist 23 | nbdist 24 | .nb-gradle 25 | **\build 26 | !**\**\src\main\**\build 27 | !**\**\src\test\**\build 28 | 29 | ### VS Code ### 30 | **\.vscode 31 | 32 | # flyctl launch added from .idea\.gitignore 33 | # Default ignored files 34 | .idea\shelf 35 | .idea\workspace.xml 36 | fly.toml 37 | -------------------------------------------------------------------------------- /frontend/app/api/stats/all/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { getAllUsers } from "@/lib/services/stats-service"; 3 | 4 | export const dynamic = "force-dynamic"; 5 | 6 | export async function GET() { 7 | try { 8 | console.log("Received request to fetch all users"); 9 | 10 | const allUsers = await getAllUsers(); 11 | 12 | return NextResponse.json({ 13 | message: "All users fetched successfully", 14 | data: allUsers, 15 | }); 16 | } catch (error) { 17 | console.error("Error fetching all users:", error); 18 | return NextResponse.json( 19 | { message: "Error fetching all users", data: null }, 20 | { status: 500 } 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/app/api/stats/top/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { getTopUsers } from "@/lib/services/stats-service"; 3 | 4 | export const dynamic = "force-dynamic"; 5 | 6 | export async function GET() { 7 | try { 8 | console.log("Received request to fetch top users"); 9 | 10 | const topUsers = await getTopUsers(); 11 | 12 | return NextResponse.json({ 13 | message: "Top users fetched successfully", 14 | data: topUsers, 15 | }); 16 | } catch (error) { 17 | console.error("Error fetching top users:", error); 18 | return NextResponse.json( 19 | { message: "Error fetching top users", data: null }, 20 | { status: 500 } 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend-old/fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for gh-wrapped-backend on 2024-12-15T19:05:47+05:30 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = 'gh-wrapped-backend' 7 | primary_region = 'iad' 8 | 9 | [build] 10 | 11 | [http_service] 12 | internal_port = 9009 13 | force_https = true 14 | auto_stop_machines = 'off' 15 | auto_start_machines = true 16 | min_machines_running = 1 17 | processes = ['app'] 18 | [[http_service.checks]] 19 | grace_period = "60s" 20 | interval = "30s" 21 | method = "GET" 22 | timeout = "15s" 23 | path = "/api/health" 24 | 25 | [[vm]] 26 | size = 'shared-cpu-1x' 27 | memory = "1gb" 28 | 29 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/repository/GitHubUserRepository.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.repository; 2 | 3 | import dev.amitwani.githubwrapped.dto.AllUserDTO; 4 | import dev.amitwani.githubwrapped.model.GitHubUser; 5 | import org.springframework.data.mongodb.repository.MongoRepository; 6 | import org.springframework.data.mongodb.repository.Query; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | @Repository 12 | public interface GitHubUserRepository extends MongoRepository { 13 | 14 | List findByUsername(String username); 15 | 16 | @Query(value = "{}", fields = "{ 'username' : 1, '_id' : 0 }") 17 | List findAllUsername(); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": [ 25 | "next-env.d.ts", 26 | "**/*.ts", 27 | "**/*.tsx", 28 | ".next/types/**/*.ts", 29 | "app/api/[username]/og/route.js" 30 | ], 31 | "exclude": ["node_modules"] 32 | } 33 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/graphql/GitHubContributionStats.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto.graphql; 2 | 3 | import dev.amitwani.githubwrapped.model.GitHubStats; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class GitHubContributionStats { 8 | 9 | private DataNode data; 10 | 11 | @Data 12 | public static class ContributionsCollection { 13 | private int commits; 14 | private int issuesClosed; 15 | private int pullRequestsClosed; 16 | private GitHubStats.ContributionCalendar contributionCalendar; 17 | } 18 | 19 | @Data 20 | public static class DataNode { 21 | private User user; 22 | } 23 | 24 | @Data 25 | public static class User { 26 | private ContributionsCollection contributionsCollection; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /frontend/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /frontend/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Button } from "@/components/ui/button"; 3 | 4 | export default function Navbar() { 5 | return ( 6 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /frontend/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function Footer() { 4 | return ( 5 |
6 |

7 | Created by{" "} 8 | 13 | Amit Wani 14 | {" "} 15 | ( 16 | 21 | X 22 | 23 | {" / "} 24 | 29 | GitHub 30 | 31 | ) 32 |

33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /backend-old/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /frontend/app/actions/stats-action.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { 4 | getStats as getStatsService, 5 | generateGitHubStats, 6 | } from "@/lib/services/stats-service"; 7 | 8 | export const generateWrapped = async (username: string) => { 9 | try { 10 | const result = await generateGitHubStats(username); 11 | 12 | if (result.status === 404) { 13 | return { error: "Invalid GitHub username" }; 14 | } 15 | 16 | if (result.status >= 400) { 17 | return { error: "Error generating wrapped" }; 18 | } 19 | 20 | return { message: result.message, data: result.data }; 21 | } catch (error) { 22 | console.error("Error generating wrapped:", error); 23 | return { error: "Error generating wrapped" }; 24 | } 25 | }; 26 | 27 | export const getStats = async (username: string) => { 28 | try { 29 | const statsDTO = await getStatsService(username); 30 | 31 | if (!statsDTO) { 32 | return null; 33 | } 34 | 35 | return { message: "Stats fetched successfully", data: statsDTO }; 36 | } catch (error) { 37 | console.error("Error fetching stats:", error); 38 | return null; 39 | } 40 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Amit Wani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/graphql/GitHubPinnedItems.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto.graphql; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class GitHubPinnedItems { 9 | 10 | private DataNode data; 11 | 12 | @Data 13 | public static class DataNode { 14 | private UserNode user; 15 | } 16 | 17 | @Data 18 | public static class UserNode { 19 | private PinnedItems pinnedItems; 20 | } 21 | 22 | @Data 23 | public static class PinnedItems { 24 | private List edges; 25 | } 26 | 27 | @Data 28 | public static class Edge { 29 | private Node node; 30 | } 31 | 32 | @Data 33 | public static class Node { 34 | private String name; 35 | private String description; 36 | private String url; 37 | private int stars; 38 | private int forkCount; 39 | private PrimaryLanguage primaryLanguage; 40 | } 41 | 42 | @Data 43 | public static class PrimaryLanguage { 44 | private String name; 45 | private String color; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/lib/mongodb.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | interface MongooseCache { 4 | conn: typeof mongoose | null; 5 | promise: Promise | null; 6 | } 7 | 8 | declare global { 9 | // eslint-disable-next-line no-var 10 | var mongooseCache: MongooseCache | undefined; 11 | } 12 | 13 | const cached: MongooseCache = global.mongooseCache ?? { 14 | conn: null, 15 | promise: null, 16 | }; 17 | 18 | if (!global.mongooseCache) { 19 | global.mongooseCache = cached; 20 | } 21 | 22 | async function dbConnect(): Promise { 23 | const MONGODB_URI = process.env.MONGODB_URI; 24 | 25 | if (!MONGODB_URI) { 26 | throw new Error("Please define the MONGODB_URI environment variable"); 27 | } 28 | 29 | if (cached.conn) { 30 | return cached.conn; 31 | } 32 | 33 | if (!cached.promise) { 34 | const opts = { 35 | bufferCommands: false, 36 | }; 37 | 38 | cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => { 39 | return mongoose; 40 | }); 41 | } 42 | 43 | try { 44 | cached.conn = await cached.promise; 45 | } catch (e) { 46 | cached.promise = null; 47 | throw e; 48 | } 49 | 50 | return cached.conn; 51 | } 52 | 53 | export default dbConnect; 54 | -------------------------------------------------------------------------------- /frontend/lib/umami.ts: -------------------------------------------------------------------------------- 1 | // Umami Analytics tracking utilities 2 | // Docs: https://umami.is/docs/tracker-functions 3 | 4 | declare global { 5 | interface Window { 6 | umami?: { 7 | track: (eventName?: string | object, eventData?: Record) => void; 8 | identify: (userId?: string | Record, userData?: Record) => void; 9 | }; 10 | } 11 | } 12 | 13 | /** 14 | * Track a custom event with Umami 15 | * @example umami.track('signup-button', { name: 'newsletter', id: 123 }) 16 | */ 17 | export function trackEvent(eventName: string, eventData?: Record) { 18 | if (typeof window !== "undefined" && window.umami) { 19 | window.umami.track(eventName, eventData); 20 | } 21 | } 22 | 23 | /** 24 | * Identify a user session with Umami 25 | * @example umami.identify('user-123', { name: 'Bob', email: 'bob@example.com' }) 26 | */ 27 | export function identifyUser(userId: string, userData?: Record) { 28 | if (typeof window !== "undefined" && window.umami) { 29 | window.umami.identify(userId, userData); 30 | } 31 | } 32 | 33 | /** 34 | * Custom hook for Umami tracking 35 | */ 36 | export function useUmami() { 37 | return { 38 | track: trackEvent, 39 | identify: identifyUser, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@octokit/rest": "^22.0.1", 13 | "@openrouter/ai-sdk-provider": "^0.0.6", 14 | "@radix-ui/react-slot": "^1.1.1", 15 | "@vercel/og": "^0.6.4", 16 | "ai": "^4.0.22", 17 | "class-variance-authority": "^0.7.1", 18 | "clsx": "^2.1.1", 19 | "html2canvas": "^1.4.1", 20 | "lucide-react": "^0.468.0", 21 | "mongoose": "^9.0.1", 22 | "next": "14.2.16", 23 | "react": "^18", 24 | "react-dom": "^18", 25 | "react-markdown": "^9.0.1", 26 | "recharts": "^2.15.0", 27 | "sharp": "^0.33.5", 28 | "simplex-noise": "^4.0.3", 29 | "tailwind-merge": "^2.5.5", 30 | "tailwindcss-animate": "^1.0.7" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20", 34 | "@types/react": "^18", 35 | "@types/react-dom": "^18", 36 | "eslint": "^8", 37 | "eslint-config-next": "14.2.16", 38 | "postcss": "^8", 39 | "tailwindcss": "^3.4.1", 40 | "typescript": "^5" 41 | }, 42 | "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c" 43 | } 44 | -------------------------------------------------------------------------------- /frontend/types/ai.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface AIAnalysisRequest { 3 | username: string | null; 4 | name: string | null 5 | bio: string | null 6 | blogUrl: string | null 7 | twitterUsername: string | null 8 | followers: number 9 | following: number 10 | publicRepos: number 11 | pinnedRepositories: PinnedRepository[] 12 | totalCommits: number | null 13 | totalIssuesClosed: number | null 14 | totalPullRequestsClosed: number | null 15 | totalStars: number | null 16 | totalForks: number | null 17 | topRepository: TopRepository | null 18 | languagesStats: LanguagesStat[] 19 | monthlyContributions: Contribution[] 20 | dailyContributions: Contribution[] 21 | longestStreak: number | null 22 | longestGap: number | null 23 | weekendActivity: number | null 24 | activeDays: number | null 25 | } 26 | 27 | export interface Contribution { 28 | name: string; 29 | total: number; 30 | } 31 | 32 | export interface PinnedRepository { 33 | name: string | null 34 | description: string | null 35 | stars: number | null 36 | forkCount: number | null 37 | topLanguage: string | null 38 | } 39 | 40 | export interface TopRepository { 41 | name: string | null 42 | topLanguage: string | null 43 | stars: number | null 44 | forks: number | null 45 | } 46 | 47 | export interface LanguagesStat { 48 | language: string | null 49 | linesCount: number | null 50 | } -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/controller/HealthController.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.controller; 2 | 3 | import dev.amitwani.githubwrapped.dto.ResponseDTO; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.availability.ApplicationAvailability; 8 | import org.springframework.boot.availability.LivenessState; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | @RequestMapping("/api/health") 16 | public class HealthController { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(HealthController.class); 19 | 20 | 21 | @Autowired 22 | ApplicationAvailability applicationAvailability; 23 | 24 | @GetMapping 25 | public ResponseEntity healthCheck() { 26 | if (applicationAvailability.getLivenessState().equals(LivenessState.CORRECT)) { 27 | return ResponseEntity.ok(new ResponseDTO("OK", null)); 28 | } else { 29 | LOGGER.error("Health check failed {}", applicationAvailability.getLivenessState()); 30 | return ResponseEntity.status(500).body(new ResponseDTO("NOT OK", null)); 31 | } 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /frontend/components/social-share.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useUmami } from "@/lib/umami"; 4 | import { useCallback } from "react"; 5 | import { YEAR } from "@/lib/constants"; 6 | 7 | export default function SocialShare({ username }: { username: string }) { 8 | const op = useUmami(); 9 | 10 | const handleShare = useCallback(() => { 11 | op.track("share_on_x", { location: "wrapped_page", username }); 12 | window.open( 13 | `https://x.com/intent/tweet?text=Check out my GitHub Wrapped for ${YEAR}! %23GitHubWrapped&url=https://githubwrapped.xyz/${username}`, 14 | "_blank" 15 | ); 16 | }, [op, username]); 17 | 18 | return ( 19 |
20 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /frontend/app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { getAllUsers } from "./actions/all-user-action"; 2 | import { MetadataRoute } from "next"; 3 | 4 | export const dynamic = "force-dynamic"; 5 | 6 | export default async function sitemap(): Promise { 7 | try { 8 | const allUsersResponse = await getAllUsers(); 9 | const allUsers = allUsersResponse.data || []; 10 | const baseUrl = 11 | process.env.NEXT_PUBLIC_APP_URL || "https://githubwrapped.xyz"; 12 | 13 | const staticRoutes: MetadataRoute.Sitemap = [ 14 | { 15 | url: baseUrl, 16 | lastModified: new Date(), 17 | changeFrequency: "daily", 18 | priority: 1, 19 | }, 20 | { 21 | url: `${baseUrl}/about`, 22 | lastModified: new Date(), 23 | changeFrequency: "daily", 24 | priority: 0.8, 25 | }, 26 | ]; 27 | 28 | const userRoutes: MetadataRoute.Sitemap = allUsers.map( 29 | (user: { username: string }) => ({ 30 | url: `${baseUrl}/${user.username}`, 31 | lastModified: new Date(), 32 | changeFrequency: "daily" as const, 33 | priority: 0.7, 34 | }) 35 | ); 36 | 37 | return [...staticRoutes, ...userRoutes]; 38 | } catch (error) { 39 | console.error("Error generating sitemap:", error); 40 | return [ 41 | { 42 | url: process.env.NEXT_PUBLIC_APP_URL || "https://githubwrapped.xyz", 43 | lastModified: new Date(), 44 | changeFrequency: "daily", 45 | priority: 1, 46 | }, 47 | ]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /frontend/app/api/stats/[username]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { getStats, generateGitHubStats } from "@/lib/services/stats-service"; 3 | 4 | export const dynamic = "force-dynamic"; 5 | 6 | export async function GET( 7 | request: NextRequest, 8 | { params }: { params: { username: string } } 9 | ) { 10 | try { 11 | const { username } = params; 12 | console.log(`Received request to fetch stats for user: ${username}`); 13 | 14 | const statsDTO = await getStats(username); 15 | 16 | if (!statsDTO) { 17 | return NextResponse.json( 18 | { message: "User stats not found", data: null }, 19 | { status: 404 } 20 | ); 21 | } 22 | 23 | return NextResponse.json({ 24 | message: "Stats fetched successfully", 25 | data: statsDTO, 26 | }); 27 | } catch (error) { 28 | console.error("Error fetching stats:", error); 29 | return NextResponse.json( 30 | { message: "Error fetching stats", data: null }, 31 | { status: 500 } 32 | ); 33 | } 34 | } 35 | 36 | export async function POST( 37 | request: NextRequest, 38 | { params }: { params: { username: string } } 39 | ) { 40 | try { 41 | const { username } = params; 42 | console.log(`Received request to generate stats for user: ${username}`); 43 | 44 | const result = await generateGitHubStats(username); 45 | 46 | return NextResponse.json( 47 | { message: result.message, data: result.data }, 48 | { status: result.status } 49 | ); 50 | } catch (error) { 51 | console.error("Error generating stats:", error); 52 | return NextResponse.json( 53 | { message: "Error generating stats", data: null }, 54 | { status: 500 } 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/model/GitHubUser.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.CreatedDate; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.mongodb.core.mapping.Document; 9 | 10 | import java.io.Serial; 11 | import java.io.Serializable; 12 | import java.util.ArrayList; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | @Data 17 | @Document("user") 18 | public class GitHubUser implements Serializable { 19 | 20 | @Serial 21 | private static final long serialVersionUID = 1L; 22 | 23 | @Id 24 | private String id; 25 | private String username; 26 | private String name; 27 | private String bio; 28 | private String email; 29 | private String company; 30 | private String location; 31 | private String avatarUrl; 32 | private String blogUrl; 33 | private String twitterUsername; 34 | private int followers; 35 | private int following; 36 | private int publicRepos; 37 | private List pinnedRepositories = new ArrayList<>(); 38 | @CreatedDate 39 | private Date createdDate; 40 | 41 | @Data 42 | @AllArgsConstructor 43 | @NoArgsConstructor 44 | public static class PinnedRepositories implements Serializable { 45 | 46 | @Serial 47 | private static final long serialVersionUID = 1L; 48 | private String name; 49 | private String description; 50 | private String url; 51 | private int stars; 52 | private int forkCount; 53 | private String topLanguage; 54 | private String topLanguageColor; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/auth/AuthFilter.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.auth; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.annotation.WebFilter; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.slf4j.Logger; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.filter.OncePerRequestFilter; 12 | 13 | import java.io.IOException; 14 | import java.time.Instant; 15 | 16 | @WebFilter 17 | @Component 18 | public class AuthFilter extends OncePerRequestFilter { 19 | 20 | private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(AuthFilter.class); 21 | 22 | @Value("${auth.token}") 23 | private String token; 24 | 25 | @Override 26 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 27 | // Validate requests 28 | if (request.getRequestURI().contains("/actuator") || request.getRequestURI().contains("/health")) { 29 | filterChain.doFilter(request, response); 30 | } else if (request.getHeader("Authorization") != null && request.getHeader("Authorization").equals(token)) { 31 | Instant start = Instant.now(); 32 | filterChain.doFilter(request, response); 33 | Instant end = Instant.now(); 34 | LOGGER.info("Request {} {} took {} ms", request.getMethod(), request.getRequestURI(), end.toEpochMilli() - start.toEpochMilli()); 35 | } else { 36 | LOGGER.warn("Unauthorized request {} from {}", request.getRequestURI(), request.getRemoteAddr()); 37 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 38 | response.setHeader("Content-Type", "application/json"); 39 | response.getWriter().write("{\"message\": \"Unauthorized\"}"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/dto/graphql/GitHubRepositoryStats.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.dto.graphql; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class GitHubRepositoryStats { 9 | 10 | private DataNode data; 11 | 12 | @Data 13 | public static class DataNode { 14 | private UserNode user; 15 | } 16 | 17 | @Data 18 | public static class UserNode { 19 | private RepositoryConnection repositories; 20 | } 21 | 22 | @Data 23 | public static class RepositoryConnection { 24 | private List edges; 25 | private PageInfo pageInfo; 26 | } 27 | 28 | @Data 29 | public static class RepositoryEdge { 30 | private RepositoryNode node; 31 | } 32 | 33 | @Data 34 | public static class RepositoryNode { 35 | private String name; 36 | private int stars; 37 | private int forkCount; 38 | private PrimaryLanguage primaryLanguage; 39 | private CommitsNode commits; 40 | private LanguageRootNode languages; 41 | } 42 | 43 | @Data 44 | public static class PrimaryLanguage { 45 | private String name; 46 | private String color; 47 | } 48 | 49 | @Data 50 | public static class LanguageRootNode { 51 | private List edges; 52 | } 53 | 54 | @Data 55 | public static class LanguageEdge { 56 | private LanguageNode node; 57 | private int size; 58 | } 59 | 60 | @Data 61 | public static class PageInfo { 62 | private boolean hasNextPage; 63 | private String endCursor; 64 | } 65 | 66 | @Data 67 | public static class CommitsNode { 68 | private TargetNode target; 69 | } 70 | 71 | @Data 72 | public static class TargetNode { 73 | private HistoryNode history; 74 | } 75 | 76 | @Data 77 | public static class HistoryNode { 78 | private int totalCount; 79 | } 80 | 81 | @Data 82 | public static class LanguageNode { 83 | private String name; 84 | private String color; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /frontend/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: GeistMono, Arial, Helvetica, sans-serif; 7 | background-color: #000; 8 | } 9 | 10 | @layer utilities { 11 | .text-balance { 12 | text-wrap: balance; 13 | } 14 | } 15 | 16 | @layer base { 17 | :root { 18 | --background: 0 0% 100%; 19 | --foreground: 0 0% 3.9%; 20 | --card: 0 0% 100%; 21 | --card-foreground: 0 0% 3.9%; 22 | --popover: 0 0% 100%; 23 | --popover-foreground: 0 0% 3.9%; 24 | --primary: 0 0% 9%; 25 | --primary-foreground: 0 0% 98%; 26 | --secondary: 0 0% 96.1%; 27 | --secondary-foreground: 0 0% 9%; 28 | --muted: 0 0% 96.1%; 29 | --muted-foreground: 0 0% 45.1%; 30 | --accent: 0 0% 96.1%; 31 | --accent-foreground: 0 0% 9%; 32 | --destructive: 0 84.2% 60.2%; 33 | --destructive-foreground: 0 0% 98%; 34 | --border: 0 0% 89.8%; 35 | --input: 0 0% 89.8%; 36 | --ring: 0 0% 3.9%; 37 | --chart-1: 12 76% 61%; 38 | --chart-2: 173 58% 39%; 39 | --chart-3: 197 37% 24%; 40 | --chart-4: 43 74% 66%; 41 | --chart-5: 27 87% 67%; 42 | --radius: 0.5rem; 43 | } 44 | .dark { 45 | --background: 0 0% 3.9%; 46 | --foreground: 0 0% 98%; 47 | --card: 0 0% 3.9%; 48 | --card-foreground: 0 0% 98%; 49 | --popover: 0 0% 3.9%; 50 | --popover-foreground: 0 0% 98%; 51 | --primary: 0 0% 98%; 52 | --primary-foreground: 0 0% 9%; 53 | --secondary: 0 0% 14.9%; 54 | --secondary-foreground: 0 0% 98%; 55 | --muted: 0 0% 14.9%; 56 | --muted-foreground: 0 0% 63.9%; 57 | --accent: 0 0% 14.9%; 58 | --accent-foreground: 0 0% 98%; 59 | --destructive: 0 62.8% 30.6%; 60 | --destructive-foreground: 0 0% 98%; 61 | --border: 0 0% 14.9%; 62 | --input: 0 0% 14.9%; 63 | --ring: 0 0% 83.1%; 64 | --chart-1: 220 70% 50%; 65 | --chart-2: 160 60% 45%; 66 | --chart-3: 30 80% 55%; 67 | --chart-4: 280 65% 60%; 68 | --chart-5: 340 75% 55%; 69 | } 70 | } 71 | 72 | @layer base { 73 | * { 74 | @apply border-border; 75 | } 76 | body { 77 | @apply bg-background text-foreground; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /frontend/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import tailwindcssAnimate from "tailwindcss-animate"; 3 | 4 | const config: Config = { 5 | darkMode: ["class"], 6 | content: [ 7 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 9 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 10 | ], 11 | theme: { 12 | extend: { 13 | colors: { 14 | background: 'hsl(var(--background))', 15 | foreground: 'hsl(var(--foreground))', 16 | card: { 17 | DEFAULT: 'hsl(var(--card))', 18 | foreground: 'hsl(var(--card-foreground))' 19 | }, 20 | popover: { 21 | DEFAULT: 'hsl(var(--popover))', 22 | foreground: 'hsl(var(--popover-foreground))' 23 | }, 24 | primary: { 25 | DEFAULT: 'hsl(var(--primary))', 26 | foreground: 'hsl(var(--primary-foreground))' 27 | }, 28 | secondary: { 29 | DEFAULT: 'hsl(var(--secondary))', 30 | foreground: 'hsl(var(--secondary-foreground))' 31 | }, 32 | muted: { 33 | DEFAULT: 'hsl(var(--muted))', 34 | foreground: 'hsl(var(--muted-foreground))' 35 | }, 36 | accent: { 37 | DEFAULT: 'hsl(var(--accent))', 38 | foreground: 'hsl(var(--accent-foreground))' 39 | }, 40 | destructive: { 41 | DEFAULT: 'hsl(var(--destructive))', 42 | foreground: 'hsl(var(--destructive-foreground))' 43 | }, 44 | border: 'hsl(var(--border))', 45 | input: 'hsl(var(--input))', 46 | ring: 'hsl(var(--ring))', 47 | chart: { 48 | '1': 'hsl(var(--chart-1))', 49 | '2': 'hsl(var(--chart-2))', 50 | '3': 'hsl(var(--chart-3))', 51 | '4': 'hsl(var(--chart-4))', 52 | '5': 'hsl(var(--chart-5))' 53 | } 54 | }, 55 | borderRadius: { 56 | lg: 'var(--radius)', 57 | md: 'calc(var(--radius) - 2px)', 58 | sm: "calc(var(--radius) - 4px)", 59 | }, 60 | keyframes: { 61 | shimmer: { 62 | "0%": { transform: "translateX(-100%)" }, 63 | "100%": { transform: "translateX(100%)" } 64 | } 65 | }, 66 | animation: { 67 | shimmer: "shimmer 1s ease-in-out infinite" 68 | } 69 | }, 70 | }, 71 | plugins: [tailwindcssAnimate], 72 | }; 73 | export default config; 74 | -------------------------------------------------------------------------------- /frontend/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |
41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |
53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |
61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /frontend/lib/models/User.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema, Document, Model } from "mongoose"; 2 | 3 | export interface IPinnedRepository { 4 | name: string; 5 | description: string | null; 6 | url: string; 7 | stars: number; 8 | forkCount: number; 9 | topLanguage: string | null; 10 | topLanguageColor: string | null; 11 | } 12 | 13 | export interface IGitHubUser extends Document { 14 | username: string; 15 | name: string | null; 16 | bio: string | null; 17 | email: string | null; 18 | company: string | null; 19 | location: string | null; 20 | avatarUrl: string; 21 | blogUrl: string | null; 22 | twitterUsername: string | null; 23 | followers: number; 24 | following: number; 25 | publicRepos: number; 26 | pinnedRepositories: IPinnedRepository[]; 27 | createdDate: Date; 28 | } 29 | 30 | const PinnedRepositorySchema = new Schema( 31 | { 32 | name: { type: String, required: true }, 33 | description: { type: String, default: null }, 34 | url: { type: String, required: true }, 35 | stars: { type: Number, default: 0 }, 36 | forkCount: { type: Number, default: 0 }, 37 | topLanguage: { type: String, default: null }, 38 | topLanguageColor: { type: String, default: null }, 39 | }, 40 | { _id: false } 41 | ); 42 | 43 | const UserSchema = new Schema( 44 | { 45 | username: { type: String, required: true, index: true }, 46 | name: { type: String, default: null }, 47 | bio: { type: String, default: null }, 48 | email: { type: String, default: null }, 49 | company: { type: String, default: null }, 50 | location: { type: String, default: null }, 51 | avatarUrl: { type: String, required: true }, 52 | blogUrl: { type: String, default: null }, 53 | twitterUsername: { type: String, default: null }, 54 | followers: { type: Number, default: 0 }, 55 | following: { type: Number, default: 0 }, 56 | publicRepos: { type: Number, default: 0 }, 57 | pinnedRepositories: { type: [PinnedRepositorySchema], default: [] }, 58 | createdDate: { type: Date, default: Date.now }, 59 | }, 60 | { 61 | collection: "user", 62 | } 63 | ); 64 | 65 | const User: Model = 66 | mongoose.models.User || mongoose.model("User", UserSchema); 67 | 68 | export default User; 69 | -------------------------------------------------------------------------------- /frontend/types/stats.ts: -------------------------------------------------------------------------------- 1 | export interface StatsResponse { 2 | message: string | null 3 | data: Data | null 4 | } 5 | 6 | export interface Data { 7 | username: string | null 8 | user: User 9 | stats: Stats 10 | } 11 | 12 | export interface User { 13 | id: string 14 | username: string | null 15 | name: string | null 16 | bio: string | null 17 | email: string | null 18 | company: string | null 19 | location: string | null 20 | avatarUrl: string 21 | blogUrl: string | null 22 | twitterUsername: string | null 23 | followers: number 24 | following: number 25 | publicRepos: number 26 | pinnedRepositories: PinnedRepository[] 27 | createdDate: string 28 | } 29 | 30 | export interface PinnedRepository { 31 | name: string | null 32 | description: string | null 33 | url: string | null 34 | stars: number | null 35 | forkCount: number | null 36 | topLanguage: string | null 37 | topLanguageColor: string | null 38 | } 39 | 40 | export interface Stats { 41 | id: string 42 | username: string | null 43 | userId: string | null 44 | totalCommits: number | null 45 | totalIssuesClosed: number | null 46 | totalPullRequestsClosed: number | null 47 | totalStars: number | null 48 | totalForks: number | null 49 | topRepository: TopRepository | null 50 | languagesStats: LanguagesStat[] 51 | contributionCalendar: ContributionCalendar 52 | monthlyContributions: MonthlyContribution[] 53 | dailyContributions: DailyContribution[] 54 | } 55 | 56 | export interface TopRepository { 57 | name: string | null 58 | topLanguage: string | null 59 | topLanguageColor: string | null 60 | stars: number | null 61 | forks: number | null 62 | } 63 | 64 | export interface LanguagesStat { 65 | language: string | null 66 | color: string | null 67 | linesCount: number | null 68 | } 69 | 70 | export interface ContributionCalendar { 71 | totalContributions: number | null 72 | weeks: Week[] 73 | } 74 | 75 | export interface Week { 76 | contributionDays: ContributionDay[] 77 | } 78 | 79 | export interface ContributionDay { 80 | weekday: number | null 81 | date: string | null 82 | contributionCount: number | null 83 | color: string | null 84 | } 85 | 86 | export interface MonthlyContribution { 87 | month: string 88 | value: number 89 | } 90 | 91 | export interface DailyContribution { 92 | day: string 93 | value: number 94 | } 95 | 96 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/controller/StatsController.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.controller; 2 | 3 | import dev.amitwani.githubwrapped.dto.AllUserDTO; 4 | import dev.amitwani.githubwrapped.dto.ResponseDTO; 5 | import dev.amitwani.githubwrapped.dto.StatsDTO; 6 | import dev.amitwani.githubwrapped.dto.TopUserDTO; 7 | import dev.amitwani.githubwrapped.service.StatsService; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/api/stats") 18 | public class StatsController { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(StatsController.class); 21 | 22 | @Autowired 23 | private StatsService statsService; 24 | 25 | @GetMapping("/{username}") 26 | public ResponseEntity getStats(@PathVariable String username) { 27 | LOGGER.info("Received request to fetch stats for user: {}", username); 28 | StatsDTO statsDTO = statsService.getStats(username); 29 | if (statsDTO == null) { 30 | return ResponseEntity.status(404).body(new ResponseDTO("User stats not found", null)); 31 | } 32 | return ResponseEntity.ok(new ResponseDTO("Stats fetched successfully", statsDTO)); 33 | } 34 | 35 | @PostMapping("/{username}") 36 | public ResponseEntity generateGitHubStats(@PathVariable String username) { 37 | LOGGER.info("Received request to generate stats for user: {}", username); 38 | return statsService.generateGitHubStats(username); 39 | } 40 | 41 | @GetMapping("/top") 42 | public ResponseEntity getTopUsers() { 43 | LOGGER.info("Received request to fetch top users"); 44 | List topUsers = statsService.getTopUsers(); 45 | return ResponseEntity.ok(new ResponseDTO("Top users fetched successfully", topUsers)); 46 | } 47 | 48 | @GetMapping("/all") 49 | public ResponseEntity getAllUsers() { 50 | LOGGER.info("Received request to fetch all users"); 51 | List allUserDTOS = statsService.getAllUsers(); 52 | return ResponseEntity.ok(new ResponseDTO("All users fetched successfully", allUserDTOS)); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /frontend/components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createContext, useContext, useState } from "react"; 4 | import { cn } from "@/lib/utils"; 5 | import { X } from "lucide-react"; 6 | 7 | type ToastType = "success" | "error" | "loading"; 8 | 9 | interface Toast { 10 | id: string; 11 | message: string; 12 | type: ToastType; 13 | } 14 | 15 | interface ToasterContextType { 16 | toast: (message: string, type: ToastType) => void; 17 | } 18 | 19 | const ToasterContext = createContext(undefined); 20 | 21 | export function useToast() { 22 | const context = useContext(ToasterContext); 23 | if (!context) { 24 | throw new Error("useToast must be used within a ToasterProvider"); 25 | } 26 | return context; 27 | } 28 | 29 | export function ToasterProvider({ children }: { children: React.ReactNode }) { 30 | const [toasts, setToasts] = useState([]); 31 | 32 | const toast = (message: string, type: ToastType) => { 33 | const id = Math.random().toString(36).slice(2); 34 | setToasts((prev) => [...prev, { id, message, type }]); 35 | if (type !== "loading") { 36 | setTimeout(() => { 37 | setToasts((prev) => prev.filter((t) => t.id !== id)); 38 | }, 3000); 39 | } 40 | }; 41 | 42 | const removeToast = (id: string) => { 43 | setToasts((prev) => prev.filter((t) => t.id !== id)); 44 | }; 45 | 46 | return ( 47 | 48 | {children} 49 |
50 | {toasts.map((toast) => ( 51 |
63 | {toast.message} 64 | {toast.type !== "loading" && ( 65 | 71 | )} 72 |
73 | ))} 74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /backend-old/src/main/java/dev/amitwani/githubwrapped/model/GitHubStats.java: -------------------------------------------------------------------------------- 1 | package dev.amitwani.githubwrapped.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.CreatedDate; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.mongodb.core.mapping.Document; 9 | 10 | import java.io.Serial; 11 | import java.io.Serializable; 12 | import java.util.ArrayList; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | @Data 17 | @Document("stats") 18 | public class GitHubStats implements Serializable { 19 | @Serial 20 | private static final long serialVersionUID = 1L; 21 | @Id 22 | private String id; 23 | private String username; 24 | private String userId; 25 | private long totalCommits; 26 | private long totalIssuesClosed; 27 | private long totalPullRequestsClosed; 28 | private long totalStars; 29 | private long totalForks; 30 | private Repository topRepository; 31 | private List languagesStats = new ArrayList<>(); 32 | private ContributionCalendar contributionCalendar; 33 | @CreatedDate 34 | private Date createdDate; 35 | 36 | @Data 37 | public static class ContributionCalendar implements Serializable { 38 | @Serial 39 | private static final long serialVersionUID = 1L; 40 | private int totalContributions; 41 | private ArrayList weeks; 42 | } 43 | 44 | 45 | @Data 46 | public static class Week implements Serializable { 47 | @Serial 48 | private static final long serialVersionUID = 1L; 49 | private ArrayList contributionDays; 50 | } 51 | 52 | @Data 53 | public static class ContributionDay implements Serializable { 54 | @Serial 55 | private static final long serialVersionUID = 1L; 56 | private int weekday; 57 | private Date date; 58 | private int contributionCount; 59 | private String color; 60 | } 61 | 62 | 63 | @Data 64 | @AllArgsConstructor 65 | @NoArgsConstructor 66 | public static class LanguageStats implements Serializable { 67 | @Serial 68 | private static final long serialVersionUID = 1L; 69 | private String language; 70 | private String color; 71 | private long linesCount; 72 | } 73 | 74 | @Data 75 | @AllArgsConstructor 76 | @NoArgsConstructor 77 | public static class Repository implements Serializable { 78 | @Serial 79 | private static final long serialVersionUID = 1L; 80 | private String name; 81 | private String topLanguage; 82 | private String topLanguageColor; 83 | private long stars; 84 | private long forks; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /backend-old/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.6 9 | 10 | 11 | dev.amitwani 12 | githubwrapped 13 | 0.0.1-SNAPSHOT 14 | githubwrapped 15 | GitHub Wrapped Backend 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 21 31 | 2023.0.4 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-data-mongodb 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-web 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-starter-circuitbreaker-resilience4j 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-devtools 49 | runtime 50 | true 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.kohsuke 59 | github-api 60 | 1.326 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 66 | 67 | 68 | 69 | 70 | org.springframework.cloud 71 | spring-cloud-dependencies 72 | ${spring-cloud.version} 73 | pom 74 | import 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-maven-plugin 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /frontend/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | import Footer from "@/components/footer"; 5 | import { ToasterProvider } from "@/components/ui/toaster"; 6 | import DonateButton from "@/components/donate-button"; 7 | import Script from "next/script"; 8 | import { YEAR } from "@/lib/constants"; 9 | 10 | const geistMono = localFont({ 11 | src: "./fonts/GeistMonoVF.woff", 12 | variable: "--font-geist-mono", 13 | weight: "100 900", 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | metadataBase: new URL("https://githubwrapped.xyz"), 18 | title: { 19 | default: `GitHub Wrapped ${YEAR}`, 20 | template: "%s | GitHub Wrapped", 21 | }, 22 | description: `Your Year in Code ${YEAR} - View your GitHub contributions, stats, and coding journey for ${YEAR}.`, 23 | keywords: [ 24 | "github", 25 | "developer", 26 | "coding", 27 | "contributions", 28 | "stats", 29 | "wrapped", 30 | YEAR, 31 | ], 32 | authors: [{ name: "Amit Wani" }], 33 | creator: "Amit Wani", 34 | openGraph: { 35 | type: "website", 36 | locale: "en_US", 37 | siteName: `GitHub Wrapped ${YEAR}`, 38 | images: [ 39 | { 40 | url: "https://githubwrapped.xyz/github-wrapped-og.png", 41 | width: 1200, 42 | height: 630, 43 | }, 44 | ], 45 | }, 46 | twitter: { 47 | card: "summary_large_image", 48 | creator: "@mtwn105", 49 | site: "@mtwn105", 50 | images: [ 51 | { 52 | url: "https://githubwrapped.xyz/github-wrapped-og.png", 53 | width: 1200, 54 | height: 630, 55 | }, 56 | ], 57 | }, 58 | robots: { 59 | index: true, 60 | follow: true, 61 | googleBot: { 62 | index: true, 63 | follow: true, 64 | "max-video-preview": -1, 65 | "max-image-preview": "large", 66 | "max-snippet": -1, 67 | }, 68 | }, 69 | }; 70 | 71 | export default function RootLayout({ 72 | children, 73 | }: Readonly<{ 74 | children: React.ReactNode; 75 | }>) { 76 | const umamiUrl = process.env.NEXT_PUBLIC_UMAMI_URL; 77 | const umamiWebsiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID; 78 | 79 | return ( 80 | 81 | 82 | 83 | {umamiUrl && umamiWebsiteId && ( 84 |