Skip to content

zerogravity-project/zerogravity-backend

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

191 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš€ ZeroGravity Backend

Typing SVG

Spring Boot REST API for Emotion Tracking & Personal Wellness Platform

Spring Boot Java MySQL Docker


πŸ“‘ Table of Contents

  1. πŸ“– Overview
  2. ✨ Key Features
  3. πŸ›  Tech Stack
  4. πŸ— Architecture
  5. πŸ“‚ Project Structure
  6. πŸ“‘ API Endpoints
  7. πŸ” Authentication Flow
  8. πŸ”§ Technical Challenges & Solutions
  9. πŸš€ Getting Started
  10. πŸ—“ Roadmap
  11. πŸ”— Related
  12. πŸ‘€ Author

πŸ“– Overview

ZeroGravity Backend is a Spring Boot REST API that powers the emotion tracking and personal wellness platform. It provides secure authentication, emotion recording, analytics, and AI-powered insights.

πŸ“Œ Part of the ZeroGravity full-stack project. Refactored from an incomplete collaborative Spring Boot project into a production-ready API. Frontend Repository | Original Vue Version

Why ZeroGravity Backend?

  • πŸ” Secure Authentication - JWT integration with NextAuth, supporting Google & Kakao OAuth
  • πŸ“Š Analytics Engine - Timezone-aware emotion statistics and chart data
  • πŸ€– AI-Powered Insights - Google Gemini API for emotion prediction and period analysis
  • πŸš€ Zero-Downtime Deploy - Build-first strategy with automatic rollback

✨ Key Features

Feature Description Tech
πŸ” JWT Authentication NextAuth integration with 15-min access / 30-day refresh tokens jjwt, Spring Security
πŸ‘€ User Management Profile, consent tracking, GDPR-compliant data deletion MyBatis, Snowflake ID
πŸ“Š Emotion Analytics Daily/Moment records, level/count/reason statistics MySQL, CONVERT_TZ
πŸ€– AI Predictions Emotion prediction from diary, period analysis Google Gemini API
πŸš€ Zero-Downtime Deploy Build-first strategy with auto-rollback Docker, GitHub Actions
πŸ”’ API Security Rate limiting, caching, security headers Nginx, Spring Security

πŸ›  Tech Stack

Category Technologies
Framework Spring Boot 3.2.5, Java 17
Database MySQL 8.0, MyBatis 3.0.3
Authentication JWT (jjwt 0.12.5), Spring Security, NextAuth Integration
AI Google Gemini API
Infrastructure Docker, Docker Compose, Nginx
Cloud OCI (Ampere A1 ARM64, Flexible Load Balancer, Object Storage)
IaC Terraform (Networking, Compute, LB, Monitoring modules)
CI/CD GitHub Actions (Zero-Downtime, Auto-Rollback)
DNS/SSL AWS Route53, Let's Encrypt (ACME, TLS 1.3)
Monitoring OCI Monitoring (CPU/Memory/Container Health Alarms, Email Alerts)
Documentation SpringDoc OpenAPI (Swagger)

πŸ— Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            OCI Cloud (Terraform-Managed)                                 β”‚
β”‚                                                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚                           VCN (Virtual Cloud Network)                               β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”‚
β”‚  β”‚  β”‚                              Public Subnet                                    β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                                                                               β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β”‚   Load Balancer   β”‚       β”‚          Compute (Ampere A1 - ARM64)        β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β”‚    (Flexible)     β”‚       β”‚                                             β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β”‚                   β”‚       β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  - TLS 1.3        │──────▢│  β”‚              Nginx                    β”‚  β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  - Let's Encrypt  β”‚  :80  β”‚  β”‚         (Reverse Proxy)               β”‚  β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  - Health Check   β”‚       β”‚  β”‚    Rate Limit: 500 req/min            β”‚  β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚                    β”‚                        β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚      β”‚                           β”‚          β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚      β–Ό                           β–Ό          β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  zerogv.com                 api.zerogv.com  β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  dev.zerogv.com         api-dev.zerogv.com  β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚      β”‚                           β”‚          β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚      β–Ό                           β–Ό          β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚           Docker Containers         β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                                     β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚  Frontend   β”‚  β”‚   Backend    β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚  (Docker)   β”‚  β”‚   (Docker)   β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚             β”‚  β”‚              β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚  Next.js 15 β”‚  β”‚ Spring Boot  β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚  standalone β”‚  β”‚    3.2.5     β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚             β”‚  β”‚              β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚ prod :3000  β”‚  β”‚  prod :8080  β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β”‚ dev  :3001  β”‚  β”‚  dev  :8081  β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                          β”‚          β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                   β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                   β”‚    MySQL     β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                   β”‚   (Docker)   β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                   β”‚              β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                   β”‚ prod :3306   β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                   β”‚ dev  :3307   β”‚  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β”‚                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β”‚                                             β”‚ β”‚  β”‚ β”‚
β”‚  β”‚  β”‚                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚  β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Object Storage  β”‚  β”‚                    OCI Monitoring                          β”‚   β”‚
β”‚  β”‚  (Static Files)  β”‚  β”‚  CPU/Memory Alarms (>80%) Β· Container Health Β· Email Alert β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

External Services:
β”œβ”€β”€ AWS Route53 (DNS: zerogv.com, api.zerogv.com, dev.zerogv.com, api-dev.zerogv.com)
β”œβ”€β”€ Google Gemini API (AI emotion prediction & period analysis)
└── OAuth Providers: Google, Kakao

CI/CD: GitHub Actions β†’ SSH β†’ Docker Build β†’ Zero-Downtime Deploy (+ Auto-Rollback)

πŸ“ Project Structure

zerogravity/src/main/java/com/zerogravity/myapp/
β”œβ”€β”€ common/               # Shared infrastructure
β”‚   β”œβ”€β”€ config/           # DB, Swagger, Web, Jackson configs
β”‚   β”œβ”€β”€ security/         # JWT, @AuthUserId annotation
β”‚   β”œβ”€β”€ exception/        # Global exception handler
β”‚   └── util/             # TimezoneUtil
β”‚
β”œβ”€β”€ auth/                 # Authentication domain
β”‚   β”œβ”€β”€ controller/       # OAuth2 endpoints
β”‚   └── dto/              # AuthResponse
β”‚
β”œβ”€β”€ user/                 # User management domain
β”‚   β”œβ”€β”€ controller/       # User profile, consent
β”‚   β”œβ”€β”€ service/          # UserService
β”‚   └── dao/              # MyBatis mapper
β”‚
β”œβ”€β”€ emotion/              # Core emotion tracking domain
β”‚   β”œβ”€β”€ controller/       # Emotion records CRUD
β”‚   β”œβ”€β”€ service/          # EmotionService, EmotionRecordService
β”‚   └── dao/              # MyBatis mappers
β”‚
β”œβ”€β”€ chart/                # Analytics domain
β”‚   β”œβ”€β”€ controller/       # Statistics endpoints
β”‚   └── service/          # ChartService
β”‚
└── ai/                   # AI features domain
    β”œβ”€β”€ controller/       # AI prediction endpoints
    └── service/          # Gemini API integration

Why Domain-Driven Architecture?

The Challenge: The original Spring Boot project had a traditional layered architecture with all controllers, services, and DAOs grouped by technical concern. This made it difficult to understand the business logic and maintain feature boundaries.

The Refactoring: Instead of a complete rewrite, I analyzed the existing codebase, preserved the working layer structure, and reorganized code by business domain while adding new features like NextAuth integration and AI capabilities.

The Result:

  • common/: Shared infrastructure (security, config, exceptions, utilities)
  • auth/: Authentication & JWT token management
  • user/: User profile and consent management
  • emotion/: Core emotion tracking (records, emotions)
  • chart/: Analytics and statistics
  • ai/: Gemini-powered predictions and analysis

This organization allows each domain to evolve independently while sharing common infrastructure.


πŸ”— API Endpoints

Base URL: https://api.zerogv.com

Authentication

Method Endpoint Description
POST /auth/verify Verify OAuth token and issue JWT
POST /auth/refresh Refresh access token
DELETE /auth/logout Logout and invalidate tokens

User

Method Endpoint Description
GET /user/me Get current user profile
DELETE /user/me Delete account
POST /user/consent Update consent status

Emotion Records

Method Endpoint Description
GET /emotions/records Get emotion records (with filters)
POST /emotions/records Create new emotion record
PUT /emotions/records/{id} Update emotion record
DELETE /emotions/records/{id} Delete emotion record

Charts & Analytics

Method Endpoint Description
GET /chart/level Get emotion level statistics
GET /chart/count Get emotion count by type
GET /chart/reason Get emotion reasons statistics

AI Features

Method Endpoint Description
POST /ai/emotion-predictions Predict emotion from diary
GET /ai/period-analyses Get AI analysis for period

πŸ” Authentication Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Client  │───▢│ NextAuth │───▢│  Backend │───▢│  MySQL   β”‚
β”‚(Frontend)β”‚    β”‚ (OAuth)  β”‚    β”‚  (JWT)   β”‚    β”‚  (User)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚               β”‚               β”‚               β”‚
     β”‚  1. OAuth     β”‚               β”‚               β”‚
     │──────────────▢│               β”‚               β”‚
     β”‚               β”‚  2. Verify    β”‚               β”‚
     β”‚               │──────────────▢│               β”‚
     β”‚               β”‚               β”‚  3. Upsert    β”‚
     β”‚               β”‚               │──────────────▢│
     β”‚               β”‚               β”‚  4. User      β”‚
     β”‚               β”‚               │◀──────────────│
     β”‚               β”‚  5. JWT       β”‚               β”‚
     β”‚               │◀──────────────│               β”‚
     β”‚  6. Session   β”‚               β”‚               β”‚
     │◀──────────────│               β”‚               β”‚

πŸ”§ Technical Challenges & Solutions

1. AI Token Optimization with Statistical Sampling

Problem: Sending all emotion records to Gemini API causes token overflow and high costs (Year period = 365+ records)

Solution:

  • Statistical Representative Sampling: Select best-matching record per time bucket
    • Year: 365 β†’ 12 records (1 per month)
    • Month: ~31 β†’ 4 records (1 per week)
    • Week: 7 β†’ 7 records (1 per day)
  • Smart Matching Algorithm: 60% emotion level + 40% reason matching
  • Daily 1.5x Weighting: Daily records weighted higher (more representative than moment)
  • Tie-breaking: score β†’ diary length β†’ reason count β†’ recency
  • Prompt Design: JSON-only response, emotion level mapping (0-6), predefined reasons

Outcome: 97% token reduction for year period (365β†’12), accurate AI analysis maintained

// Select best matching record per bucket using weighted scoring
private double calculateMatchScore(EmotionRecord record, Double targetLevel, String topReason) {
    // Daily records weighted 1.5x (more representative)
    double recordLevel = record.getEmotionId() *
        (record.getEmotionRecordType() == EmotionRecord.Type.DAILY ? 1.5 : 1.0);
    double levelScore = 1.0 - (Math.abs(recordLevel - targetLevel) / 9.0);

    // Reason matching
    double reasonScore = record.getEmotionReasons().contains(topReason) ? 1.0 : 0.0;

    // 60% level, 40% reason
    return (levelScore * 0.6) + (reasonScore * 0.4);
}

2. Timezone-Aware Data Handling

Problem: Chart grouping showed incorrect data due to server timezone (Asia/Seoul) vs user timezone mismatch

Why This Architecture:

  • Global User Support: Users can access the app from anywhere (Korea β†’ US travel scenario)
  • UTC Storage: MySQL stores all timestamps in UTC for consistency
  • Automatic Detection: Browser detects timezone via Intl.DateTimeFormat().resolvedOptions().timeZone
  • No User Selection: Timezone changes automatically when user travels to a different timezone

Solution:

  • X-Timezone Header: Frontend sends browser-detected timezone (e.g., Asia/Seoul, America/New_York)
  • SQL-level CONVERT_TZ: For grouped data (charts) - converts UTC to user timezone in query
  • Java-level Conversion: For raw timestamps - avoids JDBC double conversion issue
// Frontend axios interceptor - automatic browser timezone detection
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
config.headers['X-Timezone'] = timezone;
-- Chart grouping with user timezone (SQL-level conversion)
SELECT DATE_FORMAT(
  CONVERT_TZ(created_time, '+00:00', #{timezoneOffset}),
  '%Y-%m-%d'
) as label
FROM emotion_records

Lessons Learned (PR #62):

  • JDBC Double Conversion Issue: When JDBC driver reads MySQL timestamp, it automatically converts to JVM timezone
  • Problem Scenario: UTC in DB β†’ JDBC converts to Asia/Seoul β†’ Java CONVERT_TZ applies again = wrong time
  • Solution: Use SQL CONVERT_TZ only for grouped/aggregated data, use Java ZonedDateTime conversion for raw timestamps
// Raw timestamp conversion (Java-level to avoid JDBC double conversion)
ZonedDateTime userTime = utcTime
    .atZone(ZoneId.of("UTC"))
    .withZoneSameInstant(ZoneId.of(userTimezone));

Outcome: Correct chart grouping for users in any timezone, automatic adaptation when traveling

3. NextAuth OAuth Integration with JWT

Problem: Integrate NextAuth frontend with Spring Boot backend, supporting multiple OAuth providers

Solution:

  • Provider-based user lookup (providerId + provider combination)
  • Snowflake ID generation for distributed unique user IDs
  • JWT tokens with 15-min access / 30-day refresh lifecycle
  • Custom @AuthUserId annotation with ArgumentResolver

Outcome: Seamless OAuth login with Google/Kakao, secure JWT-based session

// @AuthUserId annotation extracts user ID from JWT
@GetMapping("/me")
public ResponseEntity<UserResponse> getMe(@AuthUserId Long userId) {
    return ResponseEntity.ok(userService.getUserById(userId));
}

4. Zero-Downtime Deployment Strategy

Problem: 502 errors during deployments when new container failed to start

Solution:

  • Build-first strategy: Build new image while old container runs
  • Image-based backup and instant rollback
  • 150-second health check (30 attempts Γ— 5 seconds)
  • Auto-rollback on health check failure

Outcome: Old container keeps running if build fails, instant rollback from backup image

# Build new image (old container still running)
docker build -t zerogv-backend:${ENV}-new .

# Backup current image
docker tag zerogv-backend:${ENV} zerogv-backend:${ENV}-backup

# Swap only after successful build
docker compose down && docker compose up -d

5. Refresh Token Security Evolution

Problem: Token rotation caused concurrent request failures and false security alerts

Solution:

  • Initially: Token rotation with reuse detection and 5-second grace period
  • Final: Simplified validation (no rotation) for stability
  • Hourly cleanup of expired/revoked tokens
  • Specific error codes (REFRESH_TOKEN_EXPIRED, REFRESH_TOKEN_INVALID)

Outcome: Stable token refresh without concurrent request errors

6. MyBatis Enum Type Handling

Problem: Database stores lowercase ('daily', 'moment') but Java uses uppercase (DAILY, MOMENT)

Solution: Custom TypeHandler extending BaseTypeHandler for case-insensitive conversion

Outcome: Seamless enum mapping between DB and Java layers

public class EmotionRecordTypeHandler extends BaseTypeHandler<EmotionRecord.Type> {
    @Override
    public EmotionRecord.Type getNullableResult(ResultSet rs, String col) {
        String value = rs.getString(col);
        return value != null
            ? EmotionRecord.Type.valueOf(value.toUpperCase())
            : null;
    }
}

πŸš€ Getting Started

Prerequisites

  • Java 17+
  • Maven 3.9+
  • Docker & Docker Compose
  • MySQL 8.0

Installation

# Clone the repository
git clone https://github.com/zerogravity-project/zerogravity-backend.git
cd zerogravity-backend

# Set up environment variables
cp .env.example .env

Development

# Local development
cd zerogravity
./mvnw spring-boot:run

# Docker deployment
docker-compose up -d

# API Documentation
open http://localhost:8080/swagger-ui.html

Environment Variables

# Database
SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/zerogravity
SPRING_DATASOURCE_USERNAME=user
SPRING_DATASOURCE_PASSWORD=password

# JWT
SPRING_JWT_SECRET=your-256-bit-secret

# AI
GEMINI_API_KEY=your-gemini-api-key

πŸ—“ Roadmap

  • Advanced AI insights with trend prediction
  • WebSocket for real-time sync
  • Rate limiting per user (not just IP)
  • Prometheus/Grafana monitoring
  • Multi-language support (i18n)

πŸ”— Related

πŸ‘€ Author

Minuk Hwang - Fullstack Developer


Made with ❀️ and β˜•

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors