Skip to content

Cunegundess/GitTrack

Repository files navigation

GitTrack Dashboard

GitTrack

A full-stack GitHub activity dashboard built with Spring Boot 3 + Vue 3

ArchitectureFeaturesEndpointsGetting StartedPerformanceTech Stack


Overview

GitTrack is a application that consumes the GitHub REST and GraphQL APIs to provide a comprehensive dashboard for tracking and visualizing any GitHub user's activity, repositories, contribution calendar, pinned repositories, and language statistics.

Built with a clean architecture separating concerns across backend (Java/Spring Boot) and frontend (Vue 3), it features a GitHub-inspired dark theme, real contribution graphs, and full Docker support.


Architecture

┌────────────────────────────────────────────────────────────────┐
│                      Frontend (Vue 3 + Vite)                    │
│  ┌──────────────── KeepAlive (persists state across tabs) ──┐  │
│  │  ┌──────────┐  ┌──────────────┐  ┌───────────┐          │  │
│  │  │Dashboard │  │ Repositories │  │ Activity  │  (Pages)  │  │
│  │  │ (1 call) │  │  (paginated) │  │(paginated)│          │  │
│  │  └────┬─────┘  └──────┬───────┘  └─────┬─────┘          │  │
│  └───────┼───────────────┼────────────────┼────────────────┘  │
│          │               │                │                    │
│  ┌───────┴───────────────┴────────────────┴─────────┐          │
│  │         GithubService (Axios)                     │          │
│  │         + Per-endpoint In-Memory Cache            │          │
│  │         + Error Interceptor (AppError)            │          │
│  └──────────────────────┬───────────────────────────┘          │
│                         │ HTTP REST (page, perPage)            │
└─────────────────────────┼──────────────────────────────────────┘
                          │
┌─────────────────────────┼────────────────────────────────────────┐
│                  Backend (Spring Boot 3.4.2)                     │
│  ┌──────────────────────┴───────────────────────────┐            │
│  │              GithubController (REST)              │            │
│  │  @RequestParam page, perPage → PagedResponse<T>  │            │
│  └──────────────────────┬───────────────────────────┘            │
│  ┌──────────────────────┴───────────────────────────┐            │
│  │              GithubService                       │            │
│  │  + @Cacheable (Caffeine, 15-min TTL)             │            │
│  │  + getDashboard()  → 1 GraphQL call              │            │
│  │  + getReposPaged() → paginated REST              │            │
│  │  + getEventsPaged()→ paginated REST              │            │
│  └──────────────────────┬───────────────────────────┘            │
│  ┌──────────────────────┴───────────────────────────┐            │
│  │   GithubClient (REST + GraphQL)                  │            │
│  │   + RateLimitTracker (pre-check X-RateLimit-*)   │            │
│  │   + RestTemplate (timeouts: 3s/5s)               │            │
│  └──────────────────────┬───────────────────────────┘            │
│  ┌──────────────────────┴───────────────────────────┐            │
│  │   DTO: PagedResponse<T> (items, page, perPage,   │            │
│  │         totalPages, totalItems)                  │            │
│  └──────────────────────────────────────────────────┘            │
└──────────────────────────┬───────────────────────────────────────┘
                           │ HTTPS
                 ┌─────────┴──────────┐
                 │   GitHub API       │
                 │ (REST + GraphQL)   │
                 └────────────────────┘

Data Flow by Tab

Dashboard tab (loaded immediately on search):

  1. User enters a GitHub username → App.vue sets searchedUsername
  2. DashboardPage mount/watch triggers getDashboard(username) via Axios
  3. Backend /api/{username}/dashboard calls GithubService.getDashboard()
  4. Service executes 1 combined GraphQL query returning: profile fields, top 100 repos, languages, pinned repos, AND contribution calendar
  5. No REST calls needed — a single GraphQL response contains everything
  6. Backend extracts profile, top 6 repos (sorted by stars), language stats (aggregated from repo edges), pinned items (6), and contribution calendar weeks
  7. Response is cached (Caffeine + frontend Map with 5-min TTL)
  8. Dashboard renders progressively per section (profile, stats, graph, languages, pinned, repos)

Repositories tab (loaded on-demand when user clicks tab):

  1. RepositoriesPage mount/watch triggers getReposPaged(username, page=1, perPage=30)
  2. Backend calls GitHub REST /users/{username}/repos?page=1&per_page=30&sort=updated
  3. Total count estimated from GraphQL repoCount.totalCount
  4. Response: PagedResponse<GithubRepository> with prev/next pagination
  5. Client-side search filter + sort (stars, name, updated) on current page
  6. Cache per page (each page has its own cache key)

Activity tab (loaded on-demand):

  1. ActivityPage mount/watch triggers getEventsPaged(username, page=1, perPage=30)
  2. Backend calls GitHub REST /users/{username}/events?page=1&per_page=30
  3. Response: PagedResponse<Map> with timeline events + prev/next buttons

Performance Optimizations

Technique Before After Reduction
Combined GraphQL (dashboard) 3 calls (REST profile + REST repos + GraphQL) 1 GraphQL call 67% fewer HTTP calls
Paginated repos (REST) per_page=100, no pagination page & perPage params Bandwidth per page
Paginated events (REST) per_page=100, no pagination page & perPage params Bandwidth per page
On-demand tab loading App.vue fetches ALL on search Each tab fetches its own data Lazy loading
KeepAlive components Tab switch re-creates + re-fetches State preserved, no re-fetch Instant tab switch
Rate limit pre-check Only reacted to 403 Checks X-RateLimit-Remaining before each call Prevents 403s
HTTP timeouts Infinite (hangs forever) Connect: 3s, Read: 5s No more hangs
Circuit breaker None 50% failure rate → open circuit for 30s Stops cascading
Frontend caching None In-memory cache with TTL per endpoint Reduces network calls
Progressive rendering All-or-nothing Per-section loading Instant profile view

Rate Limit Protection

The application implements multi-layered rate limit protection:

  1. Pre-check: Before any API call, RateLimitTracker checks if the remaining quota is exhausted
  2. Header tracking: After each response, X-RateLimit-Remaining and X-RateLimit-Reset headers are parsed and stored
  3. Automatic backoff: When rate limited, RateLimitException carries retryAfter seconds
  4. Frontend countdown: The error banner shows a live countdown and auto-retries when time expires
  5. Circuit breaker: If rate limit errors exceed 50% in a window of 10 calls, all requests are blocked for 30s

Unauthenticated users: 60 requests/hour (very restrictive — a token is strongly recommended) Authenticated users: 5,000 requests/hour (set GITHUB_API_TOKEN environment variable)


Market Differentiators

Patterns That Set This Project Apart

Pattern Implementation Why It Matters
GraphQL Batching Single query fetches profile + 100 repos + languages + pinned + contributions Eliminates N+1 problem; 1 call instead of 103
Offset Pagination with PagedResponse GET /repos?page=1&perPage=30 returning PagedResponse<T> Market-standard pagination; client controls page size
On-Demand Lazy Loading per Tab Each tab is a KeepAlive component that fetches its own data independently No wasted bandwidth; instant tab switching; scales to N tabs
Percentage-Based Language Stats Aggregates bytes across all repos, calculates (bytes/total)*100 More accurate than GitHub's per-repo view
Progressive Rendering (Section Skeleton) Each dashboard section (<div class="skeleton">) hides independently as data arrives Instant perceived load; users see profile while languages compute
Circuit Breaker + Rate Limit Pre-Check Resilience4j + X-RateLimit-Remaining header tracking Prevents cascading failures; graceful degradation under load
Frontend + Backend Dual Caching Caffeine (server, 15-min) + Map (client, 5-15 min per endpoint) Two layers of cache; server cache survives page refresh
Error Taxonomy + Retry Countdown AppError.toDisplayError() with severity, retryable flag, i18n key; countdown auto-retry User-facing resilience; rate limit shows "waiting 23s..."
GitHub Token Seamless Fallback Token present → 5,000 req/h; absent → 60 req/h; all features work either way Zero-config for demo; token for production
KeepAlive Component Caching Vue <KeepAlive> preserves tab state across switches Components are not destroyed/re-created; no data re-fetch on tab switch

Caching Strategy

All GitHub API calls are cached at both backend and frontend:

Backend (Caffeine)

  • TTL: 15 minutes
  • Max entries: 500
  • Async mode: Enabled (non-blocking cache population)
  • Cache names: userProfile, userEvents, userRepos, repoLanguages, allLanguages, userStarred, pinnedRepos, contributionCalendar, graphqlBatch, reposPaged, eventsPaged

Frontend (In-Memory Map)

  • Dashboard: 5 min TTL
  • Profile/Repos/Pinned/Contributions: 15 min TTL
  • Events: 5 min TTL
  • Cache is cleared on new search

Features

Backend

  • REST API with 9 endpoints for GitHub data
  • GraphQL API with 3 query endpoints (pinned repos, contribution calendar, top repos)
  • Caching with Caffeine for optimal performance
  • Error handling with global exception handler and proper HTTP status codes
  • Rate limit handling for GitHub API
  • GitHub token support for authenticated requests (higher rate limits)

Frontend

  • Dashboard with user profile, stats cards, contribution graph, pinned repos, language breakdown, and top repositories
  • Repositories page with search, filter, and sort capabilities
  • Activity page with animated timeline showing all GitHub events
  • Contribution graph (green squares) inspired by GitHub's own calendar
  • GitHub-inspired dark theme with proper typography, spacing, and color system
  • Responsive design for desktop, tablet, and mobile
  • Loading skeletons for better perceived performance
  • Animations including fade-in, staggered list animations, hover effects, and tooltips

Endpoints

REST API

Base URL: http://localhost:8080/api

Method Endpoint Params Description
GET /{username}/dashboard Aggregated dashboard (profile + top repos + languages + pinned + contributions) via 1 combined GraphQL query
GET /{username}/repos ?page=1&perPage=30 Paginated repositories (sorted by updated, returns PagedResponse<GithubRepository>)
GET /{username}/events ?page=1&perPage=30 Paginated recent events (returns PagedResponse<Map>)
GET /{username}/profile User profile information
GET /{username}/starred Starred repositories
GET /{username}/languages Aggregated language statistics
GET /{username}/repos/{repoName}/languages Language breakdown for a specific repo
GET /{username}/pinned Pinned repositories (via GraphQL)
GET /{username}/contributions Contribution calendar (via GraphQL)

PagedResponse format:

{
  "items": [ ... ],
  "page": 1,
  "perPage": 30,
  "totalPages": 5,
  "totalItems": 142
}

GraphQL API

Endpoint: http://localhost:8080/graphql (GraphiQL available at /graphiql)

type Query {
  pinnedRepos(username: String!): [PinnedRepo!]!
  contributionCalendar(username: String!): ContributionCalendar!
  topRepos(username: String!, limit: Int): [GithubRepository!]!
}

Example Responses

GET /api/{username}/profile

{
  "login": "octocat",
  "name": "The Octocat",
  "avatarUrl": "https://avatars.githubusercontent.com/u/583231?v=4",
  "htmlUrl": "https://github.com/octocat",
  "bio": "A cat with eight lives",
  "publicRepos": 8,
  "followers": 9000,
  "following": 9
}

GET /api/{username}/dashboard

{
  "profile": { ... },
  "topRepos": [ ... ],
  "totalRepos": 42,
  "totalStars": 1500,
  "totalForks": 300,
  "languages": [
    { "language": "TypeScript", "bytes": 250000, "percentage": 45.2 },
    { "language": "Java", "bytes": 180000, "percentage": 32.6 }
  ],
  "pinnedRepos": [ ... ],
  "contributionCalendar": {
    "totalContributions": 847,
    "weeks": [ ... ]
  }
}

Getting Started

Prerequisites

  • Java 17+
  • Node.js 18+
  • Docker & Docker Compose (optional)
  • Maven (optional, can use wrapper)

Local Development

Clone the repository

git clone https://github.com/Cunegundess/GitTrack.git
cd GitTrack

Backend

cd backend
./mvnw spring-boot:run

The backend starts on http://localhost:8080.

Frontend

cd frontend
npm install
npm run dev

The frontend starts on http://localhost:4200.

Docker Compose

docker compose up --build
  • Backend: http://localhost:8080
  • Frontend: http://localhost:4200
  • GraphiQL: http://localhost:8080/graphiql

Configuration

GitHub Token (Recommended)

Create a GitHub Personal Access Token and configure it in backend/src/main/resources/application.properties:

github.api.token=ghp_your_token_here

This enables:

  • Higher API rate limits (5,000 vs 60 requests/hour)
  • Access to GraphQL API (required for pinned repos and contribution calendar)

Tech Stack

Backend

Technology Version
Java 17
Spring Boot 3.4.2
Spring GraphQL 1.3.x
Spring Cache 3.4.x
Caffeine Cache 3.x
Maven 3.9+

Frontend

Technology Version
Vue 3.5
TypeScript 5.7
Vite 6.x
Vue Router 4.x
Axios 1.7
Vitest 3.x

Infrastructure

Technology Version
Docker Latest
Docker Compose V2

Project Structure

GitTrack/
├── backend/
│   ├── src/main/java/com/cunegundess/GitTrack/
│   │   ├── GitTrackApplication.java
│   │   ├── client/
│   │   │   └── GithubClient.java          # HTTP client with pagination + rate limit pre-check
│   │   ├── config/
│   │   │   ├── AppConfig.java              # Bean configuration + ThreadPoolTaskExecutor
│   │   │   ├── CacheConfig.java            # Caffeine cache setup
│   │   │   └── GlobalExceptionHandler.java # Centralized error handling
│   │   ├── controller/
│   │   │   ├── GithubController.java       # REST endpoints with page/perPage params
│   │   │   └── GraphQLController.java      # GraphQL resolver
│   │   ├── dto/
│   │   │   └── PagedResponse.java          # Generic pagination DTO (items, page, perPage, totalPages, totalItems)
│   │   ├── exception/
│   │   │   └── RateLimitException.java     # Rate limit error with retryAfter seconds
│   │   ├── model/
│   │   │   ├── UserProfile.java
│   │   │   ├── GithubRepository.java
│   │   │   ├── CommitActivity.java
│   │   │   ├── ContributionDay.java
│   │   │   ├── ContributionCalendar.java
│   │   │   ├── PinnedRepo.java
│   │   │   └── LanguageStat.java
│   │   └── service/
│   │       ├── GithubService.java          # Combined GraphQL dashboard + paginated repos/events
│   │       ├── GithubEventTypes.java       # Event type enum
│   │       └── RateLimitTracker.java       # X-RateLimit-Remaining/Reset pre-check
│   └── src/main/resources/
│       ├── application.properties
│       └── graphql/
│           └── schema.graphqls
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── ContributionGraph.vue       # Green squares calendar
│   │   │   ├── ErrorBanner.vue             # Rate limit countdown + retry
│   │   │   ├── LanguageSwitcher.vue        # EN/PT-BR toggle
│   │   │   ├── RepoCard.vue                # Repository card
│   │   │   ├── SearchBar.vue               # Username search
│   │   │   ├── TabContainer.vue            # Tab navigation
│   │   │   ├── ThemeToggle.vue             # Dark/light mode
│   │   │   ├── UserProfileCard.vue         # User profile card
│   │   │   ├── StatCard.vue                # Statistics card
│   │   │   ├── LanguageBar.vue             # Language distribution bar
│   │   │   └── ActivityTimeline.vue        # Event timeline
│   │   ├── pages/
│   │   │   ├── DashboardPage.vue           # 1 combined GraphQL call, progressive per-section loading
│   │   │   ├── RepositoriesPage.vue        # Paginated with prev/next + client-side filter/sort
│   │   │   └── ActivityPage.vue            # Paginated events with prev/next
│   │   ├── services/
│   │   │   └── github.service.ts           # Axios client with pagination + per-endpoint cache
│   │   ├── types/
│   │   │   ├── index.ts                    # TypeScript interfaces + PagedResponse<T>
│   │   │   └── errors.ts                   # AppError, DisplayError, ApiError
│   │   ├── locales/
│   │   │   ├── en.json                     # English translations
│   │   │   └── pt-BR.json                  # Brazilian Portuguese translations
│   │   ├── App.vue                         # Root with KeepAlive tab components
│   │   ├── main.ts                         # App entry point
│   │   └── style.css                       # Global styles + CSS variables
│   ├── index.html                          # Vite entry point
│   ├── vite.config.ts
│   └── package.json
├── docker-compose.yml
├── images/
│   └── frontend.png
└── README.md

Error Handling

The backend uses a centralized exception handler that returns consistent error responses:

{
  "timestamp": "2025-02-06T12:00:00",
  "status": 404,
  "error": "Not Found",
  "message": "GitHub user not found"
}
HTTP Status Scenario
404 User not found
403 Rate limit exceeded
401 Invalid/missing GitHub token
500 Internal server error

Development Journey

This section documents the real challenges faced during development, the reasoning behind each architectural decision, and the evolution of the codebase across multiple refactoring phases. It serves as both a knowledge base and a record of how real-world API constraints shaped the design.

Challenge Timeline

Phase Problem Root Cause Solution API Calls Per Search
1. Initial Dashboard took 5+ seconds to load 101+ sequential REST calls (1 profile + 100 per-repo language calls) N/A 103+
2. Parallel + Cache Still too slow; sequential bottleneck RestTemplate called each repo's /languages endpoint one by one CompletableFuture.allOf() parallel execution + Caffeine @Cacheable 3
3. Combined GraphQL 3 calls per dashboard was still chatty Profile (REST) + Repos (REST) + Pinned/Contribs (GraphQL) were independent Single GraphQL query with aliases returning profile + repos + languages + pinned + contributions 1
4. Pagination Repos/Events pages returned 100 items with no pagination Backend used per_page=100 without page param PagedResponse<T> DTO + @RequestParam page, perPage + frontend prev/next buttons On-demand (1 per page)
5. KeepAlive Tabs Tab switching re-created components and re-fetched data v-if destroyed component on tab deactivation <KeepAlive> preserves component state across tab switches; each tab fetches its own data 0 on tab switch
6. GraphQL Auth Wall App broke immediately without a GitHub token GitHub GraphQL API requires authentication; returns 403 Forbidden without token, which markExceeded() treated as rate limit hasToken flag → GraphQL path if token present, REST fallback otherwise 2-4 (REST) or 1 (GraphQL)
7. RateLimitTracker Permanent Block Once rate limited, app stayed blocked for 1 hour RateLimitInfo cached with Caffeine expireAfterWrite(1, HOURS); isExceeded() only checked remaining <= 0 without verifying reset time isExceeded() checks now >= resetTime and auto-invalidates; reset honored N/A
8. GraphQL 403 Misidentified 403 from unauthenticated GraphQL called markExceeded() handleHttpClientError checked status == FORBIDDEN before differentiating auth vs rate-limit Added check: if context is "graphql" and token is blank, throw UnauthorizedException instead N/A
9. REST Fallback N+1 REST fallback made N language calls per repo buildDashboardFromRest() called getAllLanguages() which iterates repos and calls getRepoLanguages per repo Aggregates primary language field from repo list (already returned by /users/{user}/repos) 0 extra

Key Technical Decisions

1. GraphQL vs REST — Why Both?

public Map<String, Object> getDashboard(String username) {
    if (hasToken) {
        try {
            return buildDashboardFromGraphQL(username);  // 1 call
        } catch (UnauthorizedException | GitHubApiException e) {
            log.warn("Falling back to REST");
        }
    }
    return buildDashboardFromRest(username);  // 2 calls
}

Decision: Dual-path. GraphQL requires authentication but delivers everything in 1 round-trip. REST works without auth but costs 2-4 calls. The hasToken flag is set once at startup from github.api.token. This ensures the app works out-of-the-box for demo (no token) and performs optimally in production (with token).

Trade-off: More code to maintain (two dashboard builders) vs single path. Worth it for zero-config demo experience.

2. GraphQL Alias — repoCount: repositories

GraphQL does not allow querying the same field twice with different arguments in the same selection set. To get both repositories { totalCount } and repositories(first: 100, ...) { nodes { ... } }, we used an alias:

repoCount: repositories {
  totalCount
}
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {field: STARGAZERS, direction: DESC}) {
  nodes { ... }
}

Decision: Aliases are the standard GraphQL solution for this pattern. Without it, we would need two separate queries (two round-trips).

3. PagedResponse<T> — Generic Pagination DTO

public class PagedResponse<T> {
    private List<T> items;
    private int page;
    private int perPage;
    private int totalPages;
    private long totalItems;
}

Decision: A generic DTO avoids duplicating pagination logic for every endpoint. The frontend PagedResponse<T> interface mirrors it exactly for type safety. totalPages is computed server-side from totalItems / perPage, keeping the client simple.

Why not cursor-based pagination? GitHub REST API uses offset-based pagination (page + per_page), so we match that. Cursor-based would require mapping GitHub's Link header, adding complexity without benefit for this use case.

4. KeepAlive + v-if — Tab Component Lifecycle

<KeepAlive>
  <DashboardPage v-if="activeTab === 'dashboard'" :username="searchedUsername" />
  <RepositoriesPage v-else-if="activeTab === 'repositories'" :username="searchedUsername" />
  <ActivityPage v-else-if="activeTab === 'activity'" :username="searchedUsername" />
</KeepAlive>

Decision: KeepAlive caches the component instance so switching tabs is instant — no re-fetch, no re-render. Each tab's watch on username fires only when the username actually changes (new search), not on tab switch. This pattern is the Vue 3 equivalent of TanStack Query's staleTime.

Alternative considered: Keeping all tab data in a parent store (Pinia). Rejected because it would couple tab loading to App.vue lifecycle and prevent independent error handling per tab.

5. Rate Limit Auto-Reset

public boolean isExceeded() {
    RateLimitInfo info = cache.getIfPresent("default");
    if (info == null) return false;
    if (info.remaining() > 0) return false;
    long now = System.currentTimeMillis() / 1000;
    if (now >= info.resetTime()) {
        cache.invalidate("default");
        return false;
    }
    return true;
}

Decision: The original implementation used atomBoolean exceeded which once set to true was never reset. By storing resetTime from X-RateLimit-Reset header and checking it on every isExceeded() call, we automatically honor GitHub's reset window without manual intervention.

6. REST Fallback Language Aggregation

Map<String, Long> langCount = new LinkedHashMap<>();
for (GithubRepository repo : repos) {
    String lang = repo.getLanguage();
    if (lang != null) langCount.merge(lang, 1L, Long::sum);
}

Decision: Instead of calling GET /repos/{owner}/{repo}/languages per repository (N+1), we aggregate the language field already present in each repo object from the /users/{user}/repos response. This gives "repo count per language" rather than "byte count per language", but uses zero additional API calls.

Trade-off: Less precise (counts repos, not bytes) vs saving N API calls. For the REST fallback path (60 req/h limit), saving calls is critical.

7. Caffeine vs Redis — Why In-Memory Cache

@Bean
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager(/* 11 cache names */);
    cacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(15, TimeUnit.MINUTES)
            .maximumSize(500)
            .recordStats());
    cacheManager.setAsyncCacheMode(true);
    return cacheManager;
}

Decision: Caffeine (in-memory) over Redis (external). The application is a single-instance Spring Boot backend — there is no horizontal scaling that would require a shared, external cache. Redis would introduce an entire infrastructure dependency (server, volume, health checks, network config) for zero benefit in the current architecture.

Why not Redis?

Concern Caffeine Redis
Deployment complexity Zero — embedded in the JAR Requires a separate Redis server + configuration
Latency Sub-microsecond (same heap) ~1-5ms per network round-trip
Cache size 500 entries ~ few MB — fits comfortably in heap Overkill for this volume
Persistence Not needed — data comes from GitHub API and can be re-fetched Would add persistence that isn't required
Operational cost None Must provision, monitor, and back up Redis

Trade-off: If the application ever needs to scale to multiple backend instances, Redis would become necessary for a shared cache. At that point, switching from Caffeine to Redis is straightforward via Spring Cache abstraction — only the CacheManager bean changes, no @Cacheable annotations need modification.

Lessons Learned

  1. Rate limits are not theoretical — GitHub's 60 req/h for unauthenticated users is extremely restrictive. Every single call counts. Design for minimal call count from day one.

  2. GraphQL requires auth — Unlike GitHub REST API (which works anonymously), GraphQL always requires a token. Plan for fallback paths.

  3. N+1 problems are insidious — A single loop (for each repo: fetch languages) consumes the entire rate limit in one page load. Always count API calls in loops.

  4. Cache invalidation is not optional — Without auto-clearing the exceeded flag, a single 403 permanently breaks the app. The isExceeded() check must consider time, not just state.

  5. KeepAlive is superior to store for tab state — Vue's <KeepAlive> preserves DOM state (scroll position, focused inputs, animation state) that a store cannot. Tab components should own their data fetching.

  6. Dual caching layers protect differently — Backend Caffeine survives server restarts and is shared across users. Frontend Map cache avoids unnecessary HTTP round-trips. Both are needed.


License

This project is licensed under the MIT License - see the LICENSE file for details.


Developed with ❤️ by <Cunegundes />

About

GitTrack is a Spring Boot application that consumes the GitHub API to track user activities such as commits, repositories created, and other interactions.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages