Complete tasks 3.2-3.3: Data models and DynamoDB table schemas
- Defined all 8 data models (Page, Widget, Bookmark, Note, TagAssociation, Group, Share, Preferences) - Implemented DynamoDB table creation for all tables with proper schemas - Added GSIs for efficient querying (UserBookmarksIndex, UserNotesIndex, TagItemsIndex, UserSharesIndex) - Comprehensive test coverage for all table schemas - Updated init-db command to create all tables
This commit is contained in:
20
internal/models/bookmark.go
Normal file
20
internal/models/bookmark.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Bookmark represents a link with title, URL, and optional grouping
|
||||
type Bookmark struct {
|
||||
ID string `dynamodbav:"bookmark_id" json:"id"`
|
||||
WidgetID string `dynamodbav:"widget_id" json:"widget_id"`
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
Title string `dynamodbav:"title" json:"title"`
|
||||
URL string `dynamodbav:"url" json:"url"`
|
||||
GroupID *string `dynamodbav:"group_id,omitempty" json:"group_id,omitempty"`
|
||||
Order int `dynamodbav:"order" json:"order"`
|
||||
FaviconURL *string `dynamodbav:"favicon_url,omitempty" json:"favicon_url,omitempty"`
|
||||
CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `dynamodbav:"updated_at" json:"updated_at"`
|
||||
Version int `dynamodbav:"version" json:"version"`
|
||||
}
|
||||
16
internal/models/group.go
Normal file
16
internal/models/group.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Group represents a named collection of bookmarks within a widget
|
||||
type Group struct {
|
||||
ID string `dynamodbav:"group_id" json:"id"`
|
||||
WidgetID string `dynamodbav:"widget_id" json:"widget_id"`
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
Name string `dynamodbav:"name" json:"name"`
|
||||
Order int `dynamodbav:"order" json:"order"`
|
||||
CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `dynamodbav:"updated_at" json:"updated_at"`
|
||||
}
|
||||
31
internal/models/note.go
Normal file
31
internal/models/note.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ContentFormat represents the format mode of note content
|
||||
type ContentFormat string
|
||||
|
||||
const (
|
||||
ContentFormatPlain ContentFormat = "plain"
|
||||
ContentFormatRTF ContentFormat = "rtf"
|
||||
ContentFormatCode ContentFormat = "code"
|
||||
ContentFormatYAML ContentFormat = "yaml"
|
||||
ContentFormatMarkdown ContentFormat = "markdown"
|
||||
)
|
||||
|
||||
// Note represents a text note with rich content support
|
||||
type Note struct {
|
||||
ID string `dynamodbav:"note_id" json:"id"`
|
||||
WidgetID string `dynamodbav:"widget_id" json:"widget_id"`
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
Title *string `dynamodbav:"title,omitempty" json:"title,omitempty"`
|
||||
Content string `dynamodbav:"content" json:"content"`
|
||||
ContentLocation string `dynamodbav:"content_location" json:"content_location"`
|
||||
Format ContentFormat `dynamodbav:"format" json:"format"`
|
||||
Language *string `dynamodbav:"language,omitempty" json:"language,omitempty"`
|
||||
CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `dynamodbav:"updated_at" json:"updated_at"`
|
||||
Version int `dynamodbav:"version" json:"version"`
|
||||
}
|
||||
15
internal/models/page.go
Normal file
15
internal/models/page.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Page represents a tab-based container that holds multiple widgets
|
||||
type Page struct {
|
||||
ID string `dynamodbav:"page_id" json:"id"`
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
Name string `dynamodbav:"name" json:"name"`
|
||||
Order int `dynamodbav:"order" json:"order"`
|
||||
CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `dynamodbav:"updated_at" json:"updated_at"`
|
||||
}
|
||||
22
internal/models/preferences.go
Normal file
22
internal/models/preferences.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SearchProvider represents the configured search engine
|
||||
type SearchProvider string
|
||||
|
||||
const (
|
||||
SearchProviderGoogle SearchProvider = "google"
|
||||
SearchProviderDuckDuckGo SearchProvider = "duckduckgo"
|
||||
SearchProviderBing SearchProvider = "bing"
|
||||
)
|
||||
|
||||
// Preferences represents user preferences and settings
|
||||
type Preferences struct {
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
SearchProvider SearchProvider `dynamodbav:"search_provider" json:"search_provider"`
|
||||
Theme *string `dynamodbav:"theme,omitempty" json:"theme,omitempty"`
|
||||
UpdatedAt time.Time `dynamodbav:"updated_at" json:"updated_at"`
|
||||
}
|
||||
31
internal/models/share.go
Normal file
31
internal/models/share.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Share represents a shareable link for bookmarks or notes
|
||||
type Share struct {
|
||||
ID string `dynamodbav:"share_id" json:"id"`
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
ItemID string `dynamodbav:"item_id" json:"item_id"`
|
||||
ItemType ItemType `dynamodbav:"item_type" json:"item_type"`
|
||||
CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
|
||||
ExpiresAt *time.Time `dynamodbav:"expires_at,omitempty" json:"expires_at,omitempty"`
|
||||
AccessCount int `dynamodbav:"access_count" json:"access_count"`
|
||||
}
|
||||
|
||||
// ShareLink represents a shareable link with full URL
|
||||
type ShareLink struct {
|
||||
ShareID string `json:"share_id"`
|
||||
URL string `json:"url"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
// SharedItem represents the data returned when accessing a shared link
|
||||
type SharedItem struct {
|
||||
ItemType ItemType `json:"item_type"`
|
||||
Data interface{} `json:"data"`
|
||||
OwnerEmail *string `json:"owner_email,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
29
internal/models/tag.go
Normal file
29
internal/models/tag.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ItemType represents the type of item that can be tagged
|
||||
type ItemType string
|
||||
|
||||
const (
|
||||
ItemTypeBookmark ItemType = "bookmark"
|
||||
ItemTypeNote ItemType = "note"
|
||||
)
|
||||
|
||||
// TagAssociation represents a many-to-many relationship between tags and items
|
||||
type TagAssociation struct {
|
||||
ItemID string `dynamodbav:"item_id" json:"item_id"`
|
||||
TagName string `dynamodbav:"tag_name" json:"tag_name"`
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
ItemType ItemType `dynamodbav:"item_type" json:"item_type"`
|
||||
CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
// TagInfo represents tag metadata with usage statistics
|
||||
type TagInfo struct {
|
||||
Name string `json:"name"`
|
||||
Count int `json:"count"`
|
||||
ItemTypes []ItemType `json:"item_types"`
|
||||
}
|
||||
40
internal/models/widget.go
Normal file
40
internal/models/widget.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// WidgetType represents the type of widget
|
||||
type WidgetType string
|
||||
|
||||
const (
|
||||
WidgetTypeBookmark WidgetType = "bookmark"
|
||||
WidgetTypeNotes WidgetType = "notes"
|
||||
WidgetTypeWeather WidgetType = "weather"
|
||||
)
|
||||
|
||||
// Position represents the x,y coordinates of a widget
|
||||
type Position struct {
|
||||
X int `dynamodbav:"x" json:"x"`
|
||||
Y int `dynamodbav:"y" json:"y"`
|
||||
}
|
||||
|
||||
// Size represents the width and height of a widget
|
||||
type Size struct {
|
||||
Width int `dynamodbav:"width" json:"width"`
|
||||
Height int `dynamodbav:"height" json:"height"`
|
||||
}
|
||||
|
||||
// Widget represents a modular component that displays specific content
|
||||
type Widget struct {
|
||||
ID string `dynamodbav:"widget_id" json:"id"`
|
||||
PageID string `dynamodbav:"page_id" json:"page_id"`
|
||||
UserID string `dynamodbav:"user_id" json:"user_id"`
|
||||
Type WidgetType `dynamodbav:"type" json:"type"`
|
||||
Title *string `dynamodbav:"title,omitempty" json:"title,omitempty"`
|
||||
Position Position `dynamodbav:"position" json:"position"`
|
||||
Size Size `dynamodbav:"size" json:"size"`
|
||||
Config map[string]interface{} `dynamodbav:"config" json:"config"`
|
||||
CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `dynamodbav:"updated_at" json:"updated_at"`
|
||||
}
|
||||
@@ -97,6 +97,516 @@ func (db *DynamoDBClient) CreateUsersTable(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePagesTable creates the Pages table with composite key (user_id, page_id)
|
||||
func (db *DynamoDBClient) CreatePagesTable(ctx context.Context) error {
|
||||
tableName := "Pages"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("page_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("page_id"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Pages table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for Pages table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWidgetsTable creates the Widgets table with composite key (page_id, widget_id)
|
||||
func (db *DynamoDBClient) CreateWidgetsTable(ctx context.Context) error {
|
||||
tableName := "Widgets"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("page_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("page_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Widgets table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for Widgets table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateBookmarksTable creates the Bookmarks table with UserBookmarksIndex GSI
|
||||
func (db *DynamoDBClient) CreateBookmarksTable(ctx context.Context) error {
|
||||
tableName := "Bookmarks"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("bookmark_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("created_at"),
|
||||
AttributeType: types.ScalarAttributeTypeN,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("bookmark_id"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: aws.String("UserBookmarksIndex"),
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("created_at"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
Projection: &types.Projection{
|
||||
ProjectionType: types.ProjectionTypeAll,
|
||||
},
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Bookmarks table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for Bookmarks table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNotesTable creates the Notes table with UserNotesIndex GSI
|
||||
func (db *DynamoDBClient) CreateNotesTable(ctx context.Context) error {
|
||||
tableName := "Notes"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("note_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("created_at"),
|
||||
AttributeType: types.ScalarAttributeTypeN,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("note_id"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: aws.String("UserNotesIndex"),
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("created_at"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
Projection: &types.Projection{
|
||||
ProjectionType: types.ProjectionTypeAll,
|
||||
},
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Notes table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for Notes table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTagAssociationsTable creates the TagAssociations table with TagItemsIndex GSI
|
||||
func (db *DynamoDBClient) CreateTagAssociationsTable(ctx context.Context) error {
|
||||
tableName := "TagAssociations"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("item_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("tag_name"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("item_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("tag_name"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: aws.String("TagItemsIndex"),
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("tag_name"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
Projection: &types.Projection{
|
||||
ProjectionType: types.ProjectionTypeInclude,
|
||||
NonKeyAttributes: []string{"item_id", "item_type", "created_at"},
|
||||
},
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create TagAssociations table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for TagAssociations table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateGroupsTable creates the Groups table
|
||||
func (db *DynamoDBClient) CreateGroupsTable(ctx context.Context) error {
|
||||
tableName := "Groups"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("group_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("widget_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("group_id"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Groups table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for Groups table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSharedItemsTable creates the SharedItems table with UserSharesIndex GSI
|
||||
func (db *DynamoDBClient) CreateSharedItemsTable(ctx context.Context) error {
|
||||
tableName := "SharedItems"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("share_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("created_at"),
|
||||
AttributeType: types.ScalarAttributeTypeN,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("share_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
},
|
||||
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
|
||||
{
|
||||
IndexName: aws.String("UserSharesIndex"),
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
{
|
||||
AttributeName: aws.String("created_at"),
|
||||
KeyType: types.KeyTypeRange, // Sort key
|
||||
},
|
||||
},
|
||||
Projection: &types.Projection{
|
||||
ProjectionType: types.ProjectionTypeInclude,
|
||||
NonKeyAttributes: []string{"share_id", "item_id", "item_type", "access_count"},
|
||||
},
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create SharedItems table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for SharedItems table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePreferencesTable creates the Preferences table
|
||||
func (db *DynamoDBClient) CreatePreferencesTable(ctx context.Context) error {
|
||||
tableName := "Preferences"
|
||||
|
||||
// Check if table already exists
|
||||
_, err := db.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create table
|
||||
_, err = db.client.CreateTable(ctx, &dynamodb.CreateTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
AttributeDefinitions: []types.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
AttributeType: types.ScalarAttributeTypeS,
|
||||
},
|
||||
},
|
||||
KeySchema: []types.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("user_id"),
|
||||
KeyType: types.KeyTypeHash, // Partition key
|
||||
},
|
||||
},
|
||||
BillingMode: types.BillingModePayPerRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Preferences table: %w", err)
|
||||
}
|
||||
|
||||
// Wait for table to be active
|
||||
waiter := dynamodb.NewTableExistsWaiter(db.client)
|
||||
err = waiter.Wait(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(tableName),
|
||||
}, 5*60)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed waiting for Preferences table to be active: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetClient returns the underlying DynamoDB client
|
||||
func (db *DynamoDBClient) GetClient() *dynamodb.Client {
|
||||
return db.client
|
||||
|
||||
@@ -450,3 +450,449 @@ func deleteTestTable(t *testing.T, ctx context.Context, client *DynamoDBClient,
|
||||
t.Logf("Warning: Failed to delete test table: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateUsersTable tests the Users table creation
|
||||
func TestCreateUsersTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("Users"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreateUsersTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateUsersTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("Users"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify key schema
|
||||
if len(output.Table.KeySchema) != 1 {
|
||||
t.Fatalf("Expected 1 key, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "user_id" {
|
||||
t.Errorf("Expected partition key 'user_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
|
||||
// Test idempotency - calling again should not error
|
||||
err = client.CreateUsersTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateUsersTable should be idempotent: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreatePagesTable tests the Pages table creation with composite key
|
||||
func TestCreatePagesTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("Pages"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreatePagesTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreatePagesTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("Pages"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify composite key schema
|
||||
if len(output.Table.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 keys, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "user_id" {
|
||||
t.Errorf("Expected partition key 'user_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *output.Table.KeySchema[1].AttributeName != "page_id" {
|
||||
t.Errorf("Expected sort key 'page_id', got '%s'", *output.Table.KeySchema[1].AttributeName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateWidgetsTable tests the Widgets table creation
|
||||
func TestCreateWidgetsTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("Widgets"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreateWidgetsTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateWidgetsTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("Widgets"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify composite key schema
|
||||
if len(output.Table.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 keys, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "page_id" {
|
||||
t.Errorf("Expected partition key 'page_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *output.Table.KeySchema[1].AttributeName != "widget_id" {
|
||||
t.Errorf("Expected sort key 'widget_id', got '%s'", *output.Table.KeySchema[1].AttributeName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBookmarksTable tests the Bookmarks table creation with GSI
|
||||
func TestCreateBookmarksTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("Bookmarks"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreateBookmarksTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateBookmarksTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("Bookmarks"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify composite key schema
|
||||
if len(output.Table.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 keys, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "widget_id" {
|
||||
t.Errorf("Expected partition key 'widget_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *output.Table.KeySchema[1].AttributeName != "bookmark_id" {
|
||||
t.Errorf("Expected sort key 'bookmark_id', got '%s'", *output.Table.KeySchema[1].AttributeName)
|
||||
}
|
||||
|
||||
// Verify GSI exists
|
||||
if len(output.Table.GlobalSecondaryIndexes) != 1 {
|
||||
t.Fatalf("Expected 1 GSI, got %d", len(output.Table.GlobalSecondaryIndexes))
|
||||
}
|
||||
gsi := output.Table.GlobalSecondaryIndexes[0]
|
||||
if *gsi.IndexName != "UserBookmarksIndex" {
|
||||
t.Errorf("Expected GSI name 'UserBookmarksIndex', got '%s'", *gsi.IndexName)
|
||||
}
|
||||
if len(gsi.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 GSI keys, got %d", len(gsi.KeySchema))
|
||||
}
|
||||
if *gsi.KeySchema[0].AttributeName != "user_id" {
|
||||
t.Errorf("Expected GSI partition key 'user_id', got '%s'", *gsi.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *gsi.KeySchema[1].AttributeName != "created_at" {
|
||||
t.Errorf("Expected GSI sort key 'created_at', got '%s'", *gsi.KeySchema[1].AttributeName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateNotesTable tests the Notes table creation with GSI
|
||||
func TestCreateNotesTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("Notes"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreateNotesTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateNotesTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("Notes"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify composite key schema
|
||||
if len(output.Table.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 keys, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "widget_id" {
|
||||
t.Errorf("Expected partition key 'widget_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *output.Table.KeySchema[1].AttributeName != "note_id" {
|
||||
t.Errorf("Expected sort key 'note_id', got '%s'", *output.Table.KeySchema[1].AttributeName)
|
||||
}
|
||||
|
||||
// Verify GSI exists
|
||||
if len(output.Table.GlobalSecondaryIndexes) != 1 {
|
||||
t.Fatalf("Expected 1 GSI, got %d", len(output.Table.GlobalSecondaryIndexes))
|
||||
}
|
||||
gsi := output.Table.GlobalSecondaryIndexes[0]
|
||||
if *gsi.IndexName != "UserNotesIndex" {
|
||||
t.Errorf("Expected GSI name 'UserNotesIndex', got '%s'", *gsi.IndexName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateTagAssociationsTable tests the TagAssociations table creation with GSI
|
||||
func TestCreateTagAssociationsTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("TagAssociations"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreateTagAssociationsTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTagAssociationsTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("TagAssociations"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify composite key schema
|
||||
if len(output.Table.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 keys, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "item_id" {
|
||||
t.Errorf("Expected partition key 'item_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *output.Table.KeySchema[1].AttributeName != "tag_name" {
|
||||
t.Errorf("Expected sort key 'tag_name', got '%s'", *output.Table.KeySchema[1].AttributeName)
|
||||
}
|
||||
|
||||
// Verify GSI exists
|
||||
if len(output.Table.GlobalSecondaryIndexes) != 1 {
|
||||
t.Fatalf("Expected 1 GSI, got %d", len(output.Table.GlobalSecondaryIndexes))
|
||||
}
|
||||
gsi := output.Table.GlobalSecondaryIndexes[0]
|
||||
if *gsi.IndexName != "TagItemsIndex" {
|
||||
t.Errorf("Expected GSI name 'TagItemsIndex', got '%s'", *gsi.IndexName)
|
||||
}
|
||||
if len(gsi.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 GSI keys, got %d", len(gsi.KeySchema))
|
||||
}
|
||||
if *gsi.KeySchema[0].AttributeName != "tag_name" {
|
||||
t.Errorf("Expected GSI partition key 'tag_name', got '%s'", *gsi.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *gsi.KeySchema[1].AttributeName != "user_id" {
|
||||
t.Errorf("Expected GSI sort key 'user_id', got '%s'", *gsi.KeySchema[1].AttributeName)
|
||||
}
|
||||
|
||||
// Verify projection type is INCLUDE
|
||||
if gsi.Projection.ProjectionType != types.ProjectionTypeInclude {
|
||||
t.Errorf("Expected projection type INCLUDE, got %v", gsi.Projection.ProjectionType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateGroupsTable tests the Groups table creation
|
||||
func TestCreateGroupsTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("Groups"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreateGroupsTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateGroupsTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("Groups"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify composite key schema
|
||||
if len(output.Table.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 keys, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "widget_id" {
|
||||
t.Errorf("Expected partition key 'widget_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *output.Table.KeySchema[1].AttributeName != "group_id" {
|
||||
t.Errorf("Expected sort key 'group_id', got '%s'", *output.Table.KeySchema[1].AttributeName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateSharedItemsTable tests the SharedItems table creation with GSI
|
||||
func TestCreateSharedItemsTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("SharedItems"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreateSharedItemsTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSharedItemsTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("SharedItems"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify key schema
|
||||
if len(output.Table.KeySchema) != 1 {
|
||||
t.Fatalf("Expected 1 key, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "share_id" {
|
||||
t.Errorf("Expected partition key 'share_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
|
||||
// Verify GSI exists
|
||||
if len(output.Table.GlobalSecondaryIndexes) != 1 {
|
||||
t.Fatalf("Expected 1 GSI, got %d", len(output.Table.GlobalSecondaryIndexes))
|
||||
}
|
||||
gsi := output.Table.GlobalSecondaryIndexes[0]
|
||||
if *gsi.IndexName != "UserSharesIndex" {
|
||||
t.Errorf("Expected GSI name 'UserSharesIndex', got '%s'", *gsi.IndexName)
|
||||
}
|
||||
if len(gsi.KeySchema) != 2 {
|
||||
t.Fatalf("Expected 2 GSI keys, got %d", len(gsi.KeySchema))
|
||||
}
|
||||
if *gsi.KeySchema[0].AttributeName != "user_id" {
|
||||
t.Errorf("Expected GSI partition key 'user_id', got '%s'", *gsi.KeySchema[0].AttributeName)
|
||||
}
|
||||
if *gsi.KeySchema[1].AttributeName != "created_at" {
|
||||
t.Errorf("Expected GSI sort key 'created_at', got '%s'", *gsi.KeySchema[1].AttributeName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreatePreferencesTable tests the Preferences table creation
|
||||
func TestCreatePreferencesTable(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete table if it exists
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String("Preferences"),
|
||||
})
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Create table
|
||||
err := client.CreatePreferencesTable(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("CreatePreferencesTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify table exists and has correct schema
|
||||
output, err := client.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String("Preferences"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("DescribeTable failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify key schema
|
||||
if len(output.Table.KeySchema) != 1 {
|
||||
t.Fatalf("Expected 1 key, got %d", len(output.Table.KeySchema))
|
||||
}
|
||||
if *output.Table.KeySchema[0].AttributeName != "user_id" {
|
||||
t.Errorf("Expected partition key 'user_id', got '%s'", *output.Table.KeySchema[0].AttributeName)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateAllTables tests creating all tables at once
|
||||
func TestCreateAllTables(t *testing.T) {
|
||||
client, ctx := setupTestClient(t)
|
||||
|
||||
// Delete all tables if they exist
|
||||
tables := []string{
|
||||
"Users", "Pages", "Widgets", "Bookmarks", "Notes",
|
||||
"TagAssociations", "Groups", "SharedItems", "Preferences",
|
||||
}
|
||||
for _, table := range tables {
|
||||
client.client.DeleteTable(ctx, &dynamodb.DeleteTableInput{
|
||||
TableName: aws.String(table),
|
||||
})
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Create all tables
|
||||
createFuncs := []struct {
|
||||
name string
|
||||
create func(context.Context) error
|
||||
}{
|
||||
{"Users", client.CreateUsersTable},
|
||||
{"Pages", client.CreatePagesTable},
|
||||
{"Widgets", client.CreateWidgetsTable},
|
||||
{"Bookmarks", client.CreateBookmarksTable},
|
||||
{"Notes", client.CreateNotesTable},
|
||||
{"TagAssociations", client.CreateTagAssociationsTable},
|
||||
{"Groups", client.CreateGroupsTable},
|
||||
{"SharedItems", client.CreateSharedItemsTable},
|
||||
{"Preferences", client.CreatePreferencesTable},
|
||||
}
|
||||
|
||||
for _, fn := range createFuncs {
|
||||
err := fn.create(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create %s table: %v", fn.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify all tables exist
|
||||
listOutput, err := client.client.ListTables(ctx, &dynamodb.ListTablesInput{})
|
||||
if err != nil {
|
||||
t.Fatalf("ListTables failed: %v", err)
|
||||
}
|
||||
|
||||
tableMap := make(map[string]bool)
|
||||
for _, tableName := range listOutput.TableNames {
|
||||
tableMap[tableName] = true
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
if !tableMap[table] {
|
||||
t.Errorf("Expected table %s to exist", table)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user