Files
Kiro/docs/task-2.1-implementation.md

4.9 KiB

Task 2.1 Implementation: User Data Model and DynamoDB Users Table

Overview

This task implements the User data model and DynamoDB Users table with full CRUD operations.

Files Created

1. User Model (internal/models/user.go)

  • Defines the User struct with all required fields:
    • ID (user_id): Unique identifier (UUID)
    • Email: User's email address
    • OAuthProvider: OAuth provider name (e.g., "google", "github")
    • OAuthID: OAuth provider's user ID
    • CreatedAt: Timestamp when user was created
    • UpdatedAt: Timestamp when user was last updated

2. DynamoDB Client (internal/storage/dynamodb.go)

  • NewDynamoDBClient: Creates a new DynamoDB client with optional endpoint override
  • CreateUsersTable: Creates the Users table with proper schema
    • Partition Key: user_id (String)
    • Billing Mode: Pay-per-request (on-demand)
    • Waits for table to be active before returning

3. User Storage Layer (internal/storage/user_storage.go)

Implements all CRUD operations:

  • CreateUser: Creates a new user with auto-generated UUID
  • GetUserByID: Retrieves a user by their ID
  • GetUserByOAuth: Retrieves a user by OAuth provider and ID
  • UpdateUser: Updates an existing user (auto-updates UpdatedAt)
  • DeleteUser: Deletes a user by their ID

4. Unit Tests (internal/storage/user_storage_test.go)

Comprehensive test coverage:

  • TestCreateUser: Verifies user creation with all fields
  • TestGetUserByID: Tests user retrieval by ID
  • TestGetUserByID_NotFound: Tests error handling for non-existent users
  • TestGetUserByOAuth: Tests user retrieval by OAuth credentials
  • TestGetUserByOAuth_NotFound: Tests error handling for OAuth lookup
  • TestUpdateUser: Verifies user updates and timestamp changes
  • TestDeleteUser: Tests user deletion
  • TestCreateUser_MultipleUsers: Verifies multiple users can be created with unique IDs

5. Database Initialization (cmd/init-db/main.go)

Standalone CLI tool to initialize DynamoDB tables:

go run cmd/init-db/main.go -endpoint http://localhost:8000

6. Table Initialization Helper (cmd/server/init_tables.go)

Helper function for the main server to initialize tables on startup.

DynamoDB Schema

Users Table

Table Name: Users
Partition Key: user_id (String)
Billing Mode: Pay-per-request

Attributes:
- user_id: String (UUID)
- email: String
- oauth_provider: String
- oauth_id: String
- created_at: Number (Unix timestamp in nanoseconds)
- updated_at: Number (Unix timestamp in nanoseconds)

Dependencies Added

  • github.com/aws/aws-sdk-go-v2: AWS SDK for Go v2
  • github.com/aws/aws-sdk-go-v2/config: AWS configuration
  • github.com/aws/aws-sdk-go-v2/service/dynamodb: DynamoDB service client
  • github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue: DynamoDB attribute marshaling
  • github.com/google/uuid: UUID generation

Running Tests

Prerequisites

  1. Start DynamoDB Local:

    make db-start
    

    Or manually:

    docker-compose up -d dynamodb-local
    
  2. Set environment variable (optional):

    export DYNAMODB_ENDPOINT=http://localhost:8000
    

Run Tests

make test
# or
go test -v ./internal/storage/...

Usage Example

package main

import (
    "context"
    "log"
    
    "custom-start-page/internal/storage"
)

func main() {
    ctx := context.Background()
    
    // Create DynamoDB client
    db, err := storage.NewDynamoDBClient(ctx, "http://localhost:8000")
    if err != nil {
        log.Fatal(err)
    }
    
    // Create Users table
    if err := db.CreateUsersTable(ctx); err != nil {
        log.Fatal(err)
    }
    
    // Create user storage
    userStorage := storage.NewUserStorage(db)
    
    // Create a new user
    user, err := userStorage.CreateUser(ctx, "user@example.com", "google", "google123")
    if err != nil {
        log.Fatal(err)
    }
    
    log.Printf("Created user: %+v", user)
    
    // Retrieve user by ID
    retrievedUser, err := userStorage.GetUserByID(ctx, user.ID)
    if err != nil {
        log.Fatal(err)
    }
    
    log.Printf("Retrieved user: %+v", retrievedUser)
}

Notes

OAuth Lookup Performance

The GetUserByOAuth method currently uses a Scan operation since there's no GSI for oauth_provider + oauth_id. For production use with many users, consider adding a GSI:

  • Partition Key: oauth_provider
  • Sort Key: oauth_id

This would change the query from O(n) scan to O(1) lookup.

Timestamp Storage

Timestamps are stored as Go time.Time which DynamoDB marshals as Unix timestamps in nanoseconds. This provides high precision for audit trails.

Requirements Validated

  • ✓ Requirement 1.1: User authentication foundation
  • ✓ Requirement 1.3: OAuth user creation and retrieval
  • ✓ Requirement 8.7: User data association

Next Steps

Task 2.2 will implement the OAuth service using this User model to handle Google OAuth authentication.