Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

23 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš— SpotSync – Smart Parking & EV Charging Reservation API

A centralized platform for busy airports and malls to manage parking zones, specifically handling the high-demand reservation of limited EV charging spots.


🌐 Live URL

Backend: https://spotsync-api-uzl2.onrender.com


πŸ› οΈ Technology Stack

Technology Package Purpose
Go go 1.22+ Programming language
Echo github.com/labstack/echo/v4 High performance, minimalist web framework
GORM gorm.io/gorm ORM for Go
PostgreSQL gorm.io/driver/postgres Relational database (NeonDB)
Validator github.com/go-playground/validator/v10 Struct validation, integrated with Echo
JWT github.com/golang-jwt/jwt/v5 Standard token generation & verification
bcrypt golang.org/x/crypto/bcrypt Password hashing
Godotenv github.com/joho/godotenv Environment variable management
Air github.com/air-verse/air Hot reloading during development

✨ Features

Authentication & Authorization

  • User registration (driver/admin roles)
  • JWT-based authentication
  • Role-based access control (driver, admin)
  • Protected routes with middleware

Parking Zones Management

  • Create parking zones (admin only)
  • View all zones with dynamic available spots calculation
  • Support for zone types: general, ev_charging, covered
  • Dynamic pricing per hour

Reservations System

  • Reserve parking spots with concurrency-safe transactions
  • Row-level locking (FOR UPDATE) to prevent over-capacity
  • View personal reservations with zone details
  • Cancel own reservations (driver)
  • View all reservations (admin)

πŸ›οΈ Project Structure (Domain-Driven Clean Architecture)

spotsync-api/
β”œβ”€β”€ cmd/
β”‚   └── main.go                    # Application entry point
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ auth/                      # JWT service interface + implementation
β”‚   β”‚   └── jwt.go
β”‚   β”œβ”€β”€ config/                    # Configuration & database connection
β”‚   β”‚   β”œβ”€β”€ config.go
β”‚   β”‚   └── db.go
β”‚   β”œβ”€β”€ domain/                    # Domain modules (each with own layers)
β”‚   β”‚   β”œβ”€β”€ user/                  # User/Auth domain
β”‚   β”‚   β”‚   β”œβ”€β”€ dto/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ request.go
β”‚   β”‚   β”‚   β”‚   └── response.go
β”‚   β”‚   β”‚   β”œβ”€β”€ entity.go
β”‚   β”‚   β”‚   β”œβ”€β”€ repository.go
β”‚   β”‚   β”‚   β”œβ”€β”€ service.go
β”‚   β”‚   β”‚   β”œβ”€β”€ handler.go
β”‚   β”‚   β”‚   └── register.go        # Route registration + DI
β”‚   β”‚   β”œβ”€β”€ zone/                  # Parking Zone domain
β”‚   β”‚   β”‚   β”œβ”€β”€ dto/
β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ request.go
β”‚   β”‚   β”‚   β”‚   └── response.go
β”‚   β”‚   β”‚   β”œβ”€β”€ entity.go
β”‚   β”‚   β”‚   β”œβ”€β”€ repository.go
β”‚   β”‚   β”‚   β”œβ”€β”€ service.go
β”‚   β”‚   β”‚   β”œβ”€β”€ handler.go
β”‚   β”‚   β”‚   └── register.go
β”‚   β”‚   └── reservation/           # Reservation domain
β”‚   β”‚       β”œβ”€β”€ dto/
β”‚   β”‚       β”‚   β”œβ”€β”€ request.go
β”‚   β”‚       β”‚   └── response.go
β”‚   β”‚       β”œβ”€β”€ entity.go
β”‚   β”‚       β”œβ”€β”€ repository.go
β”‚   β”‚       β”œβ”€β”€ service.go
β”‚   β”‚       β”œβ”€β”€ handler.go
β”‚   β”‚       └── register.go
β”‚   β”œβ”€β”€ httpresponse/              # Standardized response helpers
β”‚   β”‚   β”œβ”€β”€ response.go
β”‚   β”‚   └── error.go
β”‚   β”œβ”€β”€ middleware/                # Auth + Role middleware
β”‚   β”‚   └── auth.go
β”‚   β”œβ”€β”€ server/                    # HTTP server setup
β”‚   β”‚   └── http.go
β”‚   └── validation/                # Validation error parsing
β”‚       └── error.go
β”œβ”€β”€ postman/                       # Postman test guides
β”‚   β”œβ”€β”€ auth.postman.md
β”‚   β”œβ”€β”€ zone.postman.md
β”‚   └── reservation.postman.md
β”œβ”€β”€ doc/                           # Documentation
β”‚   └── deploy.md
β”œβ”€β”€ ref-golang/                    # Reference project (for learning)
β”œβ”€β”€ .env                           # Environment variables (not in git)
β”œβ”€β”€ .env.example                   # Example environment file
β”œβ”€β”€ .gitignore
β”œβ”€β”€ .air.toml                      # Air hot-reload config
β”œβ”€β”€ CONCEPTS.md                    # Project concepts & keywords
β”œβ”€β”€ go.mod
β”œβ”€β”€ go.sum
└── README.md

πŸ—οΈ Architecture Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        HTTP Request                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Echo Server (server/http.go)                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Middleware: Logger β†’ Recover β†’ CORS β†’ JWTAuth β†’ Role    β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Handler Layer                               β”‚
β”‚ (Bind request β†’ Validate DTO β†’ Extract JWT claims β†’ Call Serviceβ”‚
β”‚  β†’ Return JSON response)                                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Service Layer                              β”‚
β”‚  (Business logic: Hash passwords, Generate JWT, Check capacity, β”‚
β”‚   Enforce rules)                                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Repository Layer                             β”‚
β”‚  (Database operations: CRUD, Transactions, Row Locks)           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 β”‚
                                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      PostgreSQL (NeonDB)                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Dependency Injection Flow

Each domain wires its own dependencies in register.go:

Repository β†’ Service β†’ Handler β†’ Routes

πŸ” User Roles & Permissions

Role Allowed Actions
driver β€’ Register and log in
β€’ View all parking zones and availability
β€’ Reserve a parking/EV spot
β€’ View and cancel their own reservations
admin β€’ All driver permissions
β€’ Create parking zones
β€’ Set pricing for zones
β€’ View all reservations in the system

πŸ—„οΈ Database Schema

Table: users

Field Type Constraints
id SERIAL Primary Key, Auto-increment
name VARCHAR(100) NOT NULL
email VARCHAR(255) UNIQUE, NOT NULL
password VARCHAR(255) NOT NULL (bcrypt hash)
role VARCHAR(10) DEFAULT 'driver'
created_at TIMESTAMP Auto-generated
updated_at TIMESTAMP Auto-refreshed

Table: parking_zones

Field Type Constraints
id SERIAL Primary Key, Auto-increment
name VARCHAR(100) NOT NULL
type VARCHAR(20) NOT NULL (general, ev_charging, covered)
total_capacity INTEGER NOT NULL, > 0
price_per_hour DECIMAL NOT NULL, > 0
created_at TIMESTAMP Auto-generated
updated_at TIMESTAMP Auto-refreshed

Table: reservations

Field Type Constraints
id SERIAL Primary Key, Auto-increment
user_id INTEGER Foreign Key β†’ users.id
zone_id INTEGER Foreign Key β†’ parking_zones.id
license_plate VARCHAR(15) NOT NULL
status VARCHAR(15) DEFAULT 'active' (active, completed, cancelled)
created_at TIMESTAMP Auto-generated
updated_at TIMESTAMP Auto-refreshed

🌐 API Endpoints

Base URL

http://localhost:8080/api/v1

Health Check

Method URL Access
GET /health Public

πŸ”Ή Authentication Endpoints

Method URL Access Description
POST /api/v1/auth/register Public Register new user
POST /api/v1/auth/login Public Login user
GET /api/v1/auth/me Authenticated Get current user info

πŸ”Ή Parking Zone Endpoints

Method URL Access Description
GET /api/v1/zones Public Get all zones
GET /api/v1/zones/:id Public Get zone by ID
POST /api/v1/zones Admin Create new zone

πŸ”Ή Reservation Endpoints

Method URL Access Description
POST /api/v1/reservations Authenticated Create reservation
GET /api/v1/reservations/my-reservations Authenticated Get my reservations
DELETE /api/v1/reservations/:id Authenticated Cancel reservation
GET /api/v1/reservations Admin Get all reservations

πŸ“‹ Response Format

Success Response

All success responses follow a standardized wrapper format:

{
  "success": true,
  "message": "Operation description",
  "data": { ... }
}
Field Type Description
success boolean Always true for success
message string Human-readable success message
data object/array Response data (DTO object)

Example - Register User (201 Created):

{
  "success": true,
  "message": "User registered successfully",
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com",
    "role": "driver",
    "created_at": "2026-06-29T10:00:00+06:00",
    "updated_at": "2026-06-29T10:00:00+06:00"
  }
}

Example - Login (200 OK):

{
  "success": true,
  "message": "Login successful",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": 1,
      "name": "John Doe",
      "email": "john@example.com",
      "role": "driver"
    }
  }
}

Example - Get All Zones (200 OK):

{
  "success": true,
  "message": "Parking zones retrieved successfully",
  "data": [
    {
      "id": 1,
      "name": "Terminal 1 EV Charging",
      "type": "ev_charging",
      "total_capacity": 20,
      "available_spots": 14,
      "price_per_hour": 5.5,
      "created_at": "2026-06-29T10:30:00+06:00"
    }
  ]
}

Error Response

All error responses follow a standardized wrapper format:

{
  "success": false,
  "message": "Error description",
  "errors": "Error details or validation errors"
}
Field Type Description
success boolean Always false for errors
message string Human-readable error message
errors string/object/null Error details (string for simple errors, object for validation errors, null for no details)

Example - Validation Error (400 Bad Request):

{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "errors": [
      {
        "field": "Email",
        "message": "Email must be a valid email address"
      },
      {
        "field": "Password",
        "message": "Password must be at least 6 characters"
      }
    ]
  }
}

Example - Authentication Error (401 Unauthorized):

{
  "success": false,
  "message": "Missing authorization header",
  "errors": null
}

Example - Forbidden Error (403 Forbidden):

{
  "success": false,
  "message": "Admin access required",
  "errors": null
}

Example - Not Found Error (404 Not Found):

{
  "success": false,
  "message": "Parking zone not found",
  "errors": null
}

Example - Conflict Error (409 Conflict):

{
  "success": false,
  "message": "Parking zone is full",
  "errors": null
}

HTTP Status Codes

Code Usage
200 Successful GET, DELETE
201 Successful POST (resource created)
400 Validation errors, invalid input, duplicate resource
401 Missing, expired, or invalid JWT token
403 Valid token but insufficient role/permissions
404 Requested resource does not exist
409 Business logic conflict (e.g., Zone is full)
500 Unexpected server or database error

πŸš€ Getting Started

Prerequisites

  • Go 1.22 or higher
  • PostgreSQL (NeonDB, Supabase, or local)
  • Git

1. Clone the Repository

git clone https://github.com/ishtiaqrobin/spotsync-api.git
cd spotsync-api

2. Install Dependencies

go mod download

3. Configure Environment Variables

Create a .env file in the root directory:

# Option 1: Direct DSN (recommended for NeonDB)
DSN=postgresql://user:password@host.neon.tech/dbname?sslmode=require

# Option 2: Individual DB components
DB_HOST=your-neondb-host.neon.tech
DB_PORT=5432
DB_USER=your-db-user
DB_PASSWORD=your-db-password
DB_NAME=spotsync
DB_SSLMODE=require

# JWT Secret (generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
JWT_SECRET=your-super-secret-key

# Server Port
PORT=8080

4. Run the Application

Without hot-reload:

go run ./cmd/main.go

With hot-reload (Air):

# Install air if not already installed
go install github.com/air-verse/air@latest

# Run with air
air

5. Verify Installation

Visit http://localhost:8080/health β€” you should see:

{
  "status": "ok"
}

πŸ§ͺ Testing with Postman

Detailed Postman test guides are available in the postman/ directory:

Postman Environment Variables

Variable Description
base_url http://localhost:8080/api/v1
driver_token Set after driver login
admin_token Set after admin login

πŸ”’ Security Features

  • Password Hashing: bcrypt with default cost (10)
  • JWT Authentication: HS256 signed tokens with 24-hour expiry
  • Role-Based Access Control: Middleware enforces admin-only routes
  • Password Never Exposed: json:"-" tag prevents password in responses
  • CORS Enabled: Cross-origin requests supported

⚑ Concurrency Handling

The reservation system uses GORM Transactions with Row-Level Locking (FOR UPDATE) to prevent the "EV Spot Bottleneck" race condition:

db.Transaction(func(tx *gorm.DB) error {
    // 1. Lock the zone row
    tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&zone, zoneID)

    // 2. Count active reservations
    tx.Model(&Reservation{}).Where("zone_id = ? AND status = ?", zoneID, "active").Count(&count)

    // 3. Check capacity
    if activeCount >= zone.TotalCapacity {
        return ErrZoneFull
    }

    // 4. Create reservation
    tx.Create(&reservation)
    return nil
})

πŸ“¬ Deployment

Deploy to Render/Railway/Fly.io

  1. Push code to GitHub
  2. Connect repository to Render/Railway/Fly.io
  3. Set environment variables in dashboard
  4. Deploy

Database Setup (NeonDB)

  1. Go to neon.tech and create a project
  2. Copy the connection string
  3. Set as DSN in your environment variables

πŸ“ License

This project is built for educational purposes as part of the Level2-B6 Mission-9 Assignment.


πŸ‘¨β€πŸ’» Author

Ishtiaq Robin


Built using Go, Echo, and GORM

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages