In a Nutshell
A Spring Boot REST API that powers Zero Gravity's emotion logging and AI analysis. The infrastructure and deployment pipeline were built on a free-tier cloud, and the AI features were designed within budget constraints.
- 5 domains, 15 endpoints — designed auth, emotion logging, statistics, and AI analysis into a single API.
- Built infrastructure with OCI + Terraform 6 modules, and achieved Zero-Downtime deployment with a Build-first strategy.
- Reduced Gemini API payload by 97% through Time Bucket sampling, keeping the AI feature within the free-tier budget.
From team project to production.
A Spring Boot backend that started as a team project was taken to production. The project originally had only basic CRUD APIs. Authentication, infrastructure, a deployment pipeline, and AI analysis were all added on top.
- Restructured the layered architecture into a domain-based structure.
- Implemented NextAuth OAuth to JWT authentication.
- Built infrastructure on OCI and implemented Zero-Downtime deployment.
- Added Gemini API-based emotion analysis.
API Endpoints
Infrastructure
Sending everything would blow the budget in a month.
The goal was to build a feature that analyzes emotion records via the Gemini API. Sending a full year of records meant ~55K input tokens per request. That was not sustainable on the project budget.
Reducing data would hurt analysis quality, but dropping the feature was not an option since it was core functionality.
The answer was already in the existing data.
If sending everything was off the table, the solution was to pick the most representative record from each time period. The question was how to define "representative."
The Chart API's per-period average levels and per-bucket top reasons served as sampling criteria. For each bucket, the single record closest to "that month's average level, containing the top reason" was selected.
One bucket, one representative record.
The period was divided into equal time units (buckets), and the single most representative record was selected from each. Simply picking the top 12 globally could cluster them in a particular month, so splitting evenly by bucket was the better approach.
Each bucket's representative was selected by a weighted score of emotion level 60% + reason match 40%. Daily records, written as a full-day reflection, received a 1.5x weight multiplier.
Ties were broken by longest diary first, most reasons, then most recent.
Example: January bucket in a Year analysis (avg emotion level: 4.5, top reason: "Work")
AI analysis results were cached for 24 hours, and the cache was invalidated for the relevant period whenever emotion records changed.
97% payload reduction, $0.002 per request
Constraints shaped the strategy. Instead of dropping the feature, the problem was solved by reusing existing data in a new context.