166 lines
4.9 KiB
Markdown
166 lines
4.9 KiB
Markdown
# 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:
|
|
```bash
|
|
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:
|
|
```bash
|
|
make db-start
|
|
```
|
|
Or manually:
|
|
```bash
|
|
docker-compose up -d dynamodb-local
|
|
```
|
|
|
|
2. Set environment variable (optional):
|
|
```bash
|
|
export DYNAMODB_ENDPOINT=http://localhost:8000
|
|
```
|
|
|
|
### Run Tests
|
|
```bash
|
|
make test
|
|
# or
|
|
go test -v ./internal/storage/...
|
|
```
|
|
|
|
## Usage Example
|
|
|
|
```go
|
|
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.
|