From f82549298117878f0e3e1cf931cbbdf36824903b Mon Sep 17 00:00:00 2001 From: Alex Ilies Date: Sat, 20 Dec 2025 23:10:38 +0200 Subject: [PATCH 1/2] infra(gsi): gsi changes --- README.md | 1 - cmd/app/internal/database/client_test.go | 4 +- .../database/user_skill_repository.go | 6 +- .../user_skill_repository_dynamodb.go | 22 +- .../database/user_skill_repository_mock.go | 12 +- cmd/app/internal/handler/user_handler.go | 12 +- cmd/app/internal/models/user_skill.go | 2 - cmd/app/internal/service/skill_service.go | 20 +- deployments/app/cdk.go | 19 +- docs/api-testing/api-test.http | 37 +- docs/dynamodb_table_design.md | 1044 +++++++++++++++++ 11 files changed, 1124 insertions(+), 55 deletions(-) create mode 100644 docs/dynamodb_table_design.md diff --git a/README.md b/README.md index 9bbb695..9551115 100644 --- a/README.md +++ b/README.md @@ -474,7 +474,6 @@ Deployed resources (via AWS CDK in Go): - **Name**: `glad-entities` - **Optimized single table** with 1 Global Secondary Index - **Table Keys**: `EntityType` (PK) + `entity_id` (SK) -- **DynamoDB Streams**: Enabled (NEW_AND_OLD_IMAGES) - **Capacity**: On-demand billing mode - **Point-in-time recovery**: Disabled (dev-friendly) - **Removal policy**: DESTROY (dev-friendly) diff --git a/cmd/app/internal/database/client_test.go b/cmd/app/internal/database/client_test.go index b7cfc88..2b367f8 100644 --- a/cmd/app/internal/database/client_test.go +++ b/cmd/app/internal/database/client_test.go @@ -371,8 +371,8 @@ func TestMockRepository_ListUsersBySkill(t *testing.T) { repo.CreateSkill(skill2) repo.CreateSkill(skill3) - // Test list users with Go skill - skills, err := repo.ListUsersBySkill("Go") + // Test list users with Go skill in Programming category + skills, err := repo.ListUsersBySkill("Programming", "Go") if err != nil { t.Errorf("Expected no error, got %v", err) } diff --git a/cmd/app/internal/database/user_skill_repository.go b/cmd/app/internal/database/user_skill_repository.go index 4a95740..fd0a84a 100644 --- a/cmd/app/internal/database/user_skill_repository.go +++ b/cmd/app/internal/database/user_skill_repository.go @@ -9,6 +9,8 @@ type SkillRepository interface { UpdateSkill(skill *models.UserSkill) error DeleteSkill(username, skillID string) error ListSkillsForUser(username string) ([]*models.UserSkill, error) - ListUsersBySkill(skillName string) ([]*models.UserSkill, error) - ListUsersBySkillAndLevel(skillName string, proficiencyLevel models.ProficiencyLevel) ([]*models.UserSkill, error) + // ListUsersBySkill queries the BySkill GSI with Category + SkillName + ListUsersBySkill(category, skillName string) ([]*models.UserSkill, error) + // ListUsersBySkillAndLevel queries the BySkill GSI with Category + SkillName + ProficiencyLevel + ListUsersBySkillAndLevel(category, skillName string, proficiencyLevel models.ProficiencyLevel) ([]*models.UserSkill, error) } diff --git a/cmd/app/internal/database/user_skill_repository_dynamodb.go b/cmd/app/internal/database/user_skill_repository_dynamodb.go index 1e192b5..558fb0f 100644 --- a/cmd/app/internal/database/user_skill_repository_dynamodb.go +++ b/cmd/app/internal/database/user_skill_repository_dynamodb.go @@ -34,7 +34,6 @@ func (r *DynamoDBRepository) CreateSkill(skill *models.UserSkill) error { ConditionExpression: aws.String("attribute_not_exists(entity_id)"), } - _, err = r.client.PutItem(input) _, err = r.client.PutItem(input) if err != nil { log.Error("Failed to create skill in DynamoDB", "error", err.Error(), "duration", time.Since(start)) @@ -182,8 +181,9 @@ func (r *DynamoDBRepository) ListSkillsForUser(username string) ([]*models.UserS } // ListUsersBySkill retrieves all users who have a specific skill using GSI BySkill -func (r *DynamoDBRepository) ListUsersBySkill(skillName string) ([]*models.UserSkill, error) { - log := logger.WithComponent("database").With("operation", "ListUsersBySkill", "skill", skillName) +// GSI BySkill structure: PK=Category, SK=SkillName+ProficiencyLevel+YearsOfExperience+Username +func (r *DynamoDBRepository) ListUsersBySkill(category, skillName string) ([]*models.UserSkill, error) { + log := logger.WithComponent("database").With("operation", "ListUsersBySkill", "category", category, "skill", skillName) start := time.Now() log.Debug("Starting users list retrieval by skill") @@ -191,8 +191,9 @@ func (r *DynamoDBRepository) ListUsersBySkill(skillName string) ([]*models.UserS input := &dynamodb.QueryInput{ TableName: aws.String(TableName), IndexName: aws.String(GSIBySkill), - KeyConditionExpression: aws.String("SkillName = :skillName"), + KeyConditionExpression: aws.String("Category = :category AND SkillName = :skillName"), ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":category": {S: aws.String(category)}, ":skillName": {S: aws.String(skillName)}, }, } @@ -213,13 +214,15 @@ func (r *DynamoDBRepository) ListUsersBySkill(skillName string) ([]*models.UserS skills = append(skills, &skill) } - log.Info("Users with skill retrieved successfully", "skill", skillName, "count", len(skills), "duration", time.Since(start)) + log.Info("Users with skill retrieved successfully", "category", category, "skill", skillName, "count", len(skills), "duration", time.Since(start)) return skills, nil } // ListUsersBySkillAndLevel retrieves users with a specific skill at a specific proficiency level -func (r *DynamoDBRepository) ListUsersBySkillAndLevel(skillName string, proficiencyLevel models.ProficiencyLevel) ([]*models.UserSkill, error) { - log := logger.WithComponent("database").With("operation", "ListUsersBySkillAndLevel", "skill", skillName, "level", proficiencyLevel) +// GSI BySkill structure: PK=Category, SK=SkillName+ProficiencyLevel+YearsOfExperience+Username +// Uses composite sort key matching: Category + SkillName + ProficiencyLevel (left-to-right) +func (r *DynamoDBRepository) ListUsersBySkillAndLevel(category, skillName string, proficiencyLevel models.ProficiencyLevel) ([]*models.UserSkill, error) { + log := logger.WithComponent("database").With("operation", "ListUsersBySkillAndLevel", "category", category, "skill", skillName, "level", proficiencyLevel) start := time.Now() log.Debug("Starting users list retrieval by skill and level") @@ -227,8 +230,9 @@ func (r *DynamoDBRepository) ListUsersBySkillAndLevel(skillName string, proficie input := &dynamodb.QueryInput{ TableName: aws.String(TableName), IndexName: aws.String(GSIBySkill), - KeyConditionExpression: aws.String("SkillName = :skillName AND ProficiencyLevel = :level"), + KeyConditionExpression: aws.String("Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level"), ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":category": {S: aws.String(category)}, ":skillName": {S: aws.String(skillName)}, ":level": {S: aws.String(string(proficiencyLevel))}, }, @@ -250,6 +254,6 @@ func (r *DynamoDBRepository) ListUsersBySkillAndLevel(skillName string, proficie skills = append(skills, &skill) } - log.Info("Users with skill and level retrieved successfully", "skill", skillName, "level", proficiencyLevel, "count", len(skills), "duration", time.Since(start)) + log.Info("Users with skill and level retrieved successfully", "category", category, "skill", skillName, "level", proficiencyLevel, "count", len(skills), "duration", time.Since(start)) return skills, nil } diff --git a/cmd/app/internal/database/user_skill_repository_mock.go b/cmd/app/internal/database/user_skill_repository_mock.go index f57da15..634fcbf 100644 --- a/cmd/app/internal/database/user_skill_repository_mock.go +++ b/cmd/app/internal/database/user_skill_repository_mock.go @@ -114,8 +114,8 @@ func (m *MockRepository) ListSkillsForUser(username string) ([]*models.UserSkill } // ListUsersBySkill retrieves all users with a specific skill from memory -func (m *MockRepository) ListUsersBySkill(skillName string) ([]*models.UserSkill, error) { - log := logger.WithComponent("database").With("operation", "ListUsersBySkill", "skill", skillName, "repository", "mock") +func (m *MockRepository) ListUsersBySkill(category, skillName string) ([]*models.UserSkill, error) { + log := logger.WithComponent("database").With("operation", "ListUsersBySkill", "category", category, "skill", skillName, "repository", "mock") start := time.Now() log.Debug("Starting users list retrieval by skill from mock repository") @@ -125,7 +125,7 @@ func (m *MockRepository) ListUsersBySkill(skillName string) ([]*models.UserSkill var skills []*models.UserSkill for _, skill := range m.skills { - if skill.SkillName == skillName { + if skill.Category == category && skill.SkillName == skillName { skills = append(skills, skill) } } @@ -135,8 +135,8 @@ func (m *MockRepository) ListUsersBySkill(skillName string) ([]*models.UserSkill } // ListUsersBySkillAndLevel retrieves all users with a specific skill and proficiency level from memory -func (m *MockRepository) ListUsersBySkillAndLevel(skillName string, proficiencyLevel models.ProficiencyLevel) ([]*models.UserSkill, error) { - log := logger.WithComponent("database").With("operation", "ListUsersBySkillAndLevel", "skill", skillName, "level", proficiencyLevel, "repository", "mock") +func (m *MockRepository) ListUsersBySkillAndLevel(category, skillName string, proficiencyLevel models.ProficiencyLevel) ([]*models.UserSkill, error) { + log := logger.WithComponent("database").With("operation", "ListUsersBySkillAndLevel", "category", category, "skill", skillName, "level", proficiencyLevel, "repository", "mock") start := time.Now() log.Debug("Starting users list retrieval by skill and level from mock repository") @@ -146,7 +146,7 @@ func (m *MockRepository) ListUsersBySkillAndLevel(skillName string, proficiencyL var skills []*models.UserSkill for _, skill := range m.skills { - if skill.SkillName == skillName && skill.ProficiencyLevel == proficiencyLevel { + if skill.Category == category && skill.SkillName == skillName && skill.ProficiencyLevel == proficiencyLevel { skills = append(skills, skill) } } diff --git a/cmd/app/internal/handler/user_handler.go b/cmd/app/internal/handler/user_handler.go index 3f1fc7d..9ff97ad 100644 --- a/cmd/app/internal/handler/user_handler.go +++ b/cmd/app/internal/handler/user_handler.go @@ -313,7 +313,7 @@ func (h *Handler) DeleteSkill(request events.APIGatewayProxyRequest) (events.API } // ListUsersBySkill handles finding all users with a specific skill -// GET /skills/{skillName}/users +// GET /skills/{skillName}/users?category=&level= func (h *Handler) ListUsersBySkill(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { // Get skill name from path parameter skillName, ok := request.PathParameters["skillName"] @@ -321,12 +321,18 @@ func (h *Handler) ListUsersBySkill(request events.APIGatewayProxyRequest) (event return errorResponse(http.StatusBadRequest, "Skill name is required"), nil } + // Get category from query parameters (required for multi-key GSI) + category, ok := request.QueryStringParameters["category"] + if !ok || category == "" { + return errorResponse(http.StatusBadRequest, "Category is required"), nil + } + // Check for proficiency level filter in query parameters proficiencyLevel, ok := request.QueryStringParameters["level"] if ok && proficiencyLevel != "" { // Query with level filter level := models.ProficiencyLevel(proficiencyLevel) - users, err := h.skillService.ListUsersBySkillAndLevel(skillName, level) + users, err := h.skillService.ListUsersBySkillAndLevel(category, skillName, level) if err != nil { return h.handleServiceError(err), nil } @@ -334,7 +340,7 @@ func (h *Handler) ListUsersBySkill(request events.APIGatewayProxyRequest) (event } // Query all users with skill - users, err := h.skillService.ListUsersBySkill(skillName) + users, err := h.skillService.ListUsersBySkill(category, skillName) if err != nil { return h.handleServiceError(err), nil } diff --git a/cmd/app/internal/models/user_skill.go b/cmd/app/internal/models/user_skill.go index bf3f99f..0a9b320 100644 --- a/cmd/app/internal/models/user_skill.go +++ b/cmd/app/internal/models/user_skill.go @@ -100,8 +100,6 @@ func (s *UserSkill) SetKeys() { // Base table key: Unique identifier s.EntityID = BuildUserSkillEntityID(s.Username, s.SkillID) s.EntityType = "UserSkill" - // Composite sort key: ProficiencyLevel#Username - s.SkillCompositeSort = string(s.ProficiencyLevel) + "#" + s.Username } // UpdateProficiency updates the skill proficiency level diff --git a/cmd/app/internal/service/skill_service.go b/cmd/app/internal/service/skill_service.go index 7e14021..52fdd28 100644 --- a/cmd/app/internal/service/skill_service.go +++ b/cmd/app/internal/service/skill_service.go @@ -179,14 +179,14 @@ func (s *SkillService) ListSkillsForUser(username string) ([]dto.SkillResponse, return result, nil } -// ListUsersBySkill retrieves all users who have a specific skill -func (s *SkillService) ListUsersBySkill(skillName string) ([]dto.UserSkillResponse, error) { - log := logger.WithComponent("service").With("operation", "ListUsersBySkill", "skill", skillName) +// ListUsersBySkill retrieves all users who have a specific skill in a category +func (s *SkillService) ListUsersBySkill(category, skillName string) ([]dto.UserSkillResponse, error) { + log := logger.WithComponent("service").With("operation", "ListUsersBySkill", "category", category, "skill", skillName) start := time.Now() log.Info("Retrieving users by skill") - skills, err := s.repo.ListUsersBySkill(skillName) + skills, err := s.repo.ListUsersBySkill(category, skillName) if err != nil { log.Error("Failed to retrieve users by skill", "error", err.Error(), "duration", time.Since(start)) return nil, err @@ -205,18 +205,18 @@ func (s *SkillService) ListUsersBySkill(skillName string) ([]dto.UserSkillRespon } } - log.Info("Users with skill retrieved successfully", "skill", skillName, "count", len(result), "duration", time.Since(start)) + log.Info("Users with skill retrieved successfully", "category", category, "skill", skillName, "count", len(result), "duration", time.Since(start)) return result, nil } -// ListUsersBySkillAndLevel retrieves users with a skill at a specific proficiency level -func (s *SkillService) ListUsersBySkillAndLevel(skillName string, proficiencyLevel models.ProficiencyLevel) ([]dto.UserSkillResponse, error) { - log := logger.WithComponent("service").With("operation", "ListUsersBySkillAndLevel", "skill", skillName, "level", proficiencyLevel) +// ListUsersBySkillAndLevel retrieves users with a skill at a specific proficiency level in a category +func (s *SkillService) ListUsersBySkillAndLevel(category, skillName string, proficiencyLevel models.ProficiencyLevel) ([]dto.UserSkillResponse, error) { + log := logger.WithComponent("service").With("operation", "ListUsersBySkillAndLevel", "category", category, "skill", skillName, "level", proficiencyLevel) start := time.Now() log.Info("Retrieving users by skill and level") - skills, err := s.repo.ListUsersBySkillAndLevel(skillName, proficiencyLevel) + skills, err := s.repo.ListUsersBySkillAndLevel(category, skillName, proficiencyLevel) if err != nil { log.Error("Failed to retrieve users by skill and level", "error", err.Error(), "duration", time.Since(start)) return nil, err @@ -235,6 +235,6 @@ func (s *SkillService) ListUsersBySkillAndLevel(skillName string, proficiencyLev } } - log.Info("Users with skill and level retrieved successfully", "skill", skillName, "level", proficiencyLevel, "count", len(result), "duration", time.Since(start)) + log.Info("Users with skill and level retrieved successfully", "category", category, "skill", skillName, "level", proficiencyLevel, "count", len(result), "duration", time.Since(start)) return result, nil } diff --git a/deployments/app/cdk.go b/deployments/app/cdk.go index 8b77cb5..56849df 100644 --- a/deployments/app/cdk.go +++ b/deployments/app/cdk.go @@ -31,18 +31,33 @@ func createEntitiesTable(stack awscdk.Stack, id *string, environment string) aws }, GlobalSecondaryIndexes: &[]*awsdynamodb.GlobalSecondaryIndexPropsV2{ - // GSI for skill-only queries + // GSI for flexible category/skill/proficiency queries + // Single PK: Category (allows broad queries) + // Composite SK: SkillName + ProficiencyLevel + YearsOfExperience + Username + // This design provides maximum query flexibility: + // - Query by Category alone + // - Query by Category + SkillName + // - Query by Category + SkillName + ProficiencyLevel + // - Query by Category + SkillName + ProficiencyLevel + YearsOfExperience (with range) { IndexName: jsii.String("BySkill"), PartitionKey: &awsdynamodb.Attribute{ - Name: jsii.String("SkillName"), + Name: jsii.String("Category"), Type: awsdynamodb.AttributeType_STRING, }, SortKeys: &[]*awsdynamodb.Attribute{ + { + Name: jsii.String("SkillName"), + Type: awsdynamodb.AttributeType_STRING, + }, { Name: jsii.String("ProficiencyLevel"), Type: awsdynamodb.AttributeType_STRING, }, + { + Name: jsii.String("YearsOfExperience"), + Type: awsdynamodb.AttributeType_NUMBER, + }, { Name: jsii.String("Username"), Type: awsdynamodb.AttributeType_STRING, diff --git a/docs/api-testing/api-test.http b/docs/api-testing/api-test.http index e91426d..f7ab068 100644 --- a/docs/api-testing/api-test.http +++ b/docs/api-testing/api-test.http @@ -66,7 +66,7 @@ Content-Type: application/json { "username": "jane_doe", - "password": "secure123" + "password": "newPassword123" } ### 1.7 Login - Alice @@ -331,7 +331,7 @@ Authorization: Bearer {{token}} Content-Type: application/json { - "skill_name": "JavaScripdt", + "skill_name": "JavaScript", "proficiency_level": "Advanced", "years_of_experience": 5, "notes": "Frontend development with React" @@ -421,50 +421,51 @@ Authorization: Bearer {{token}} ############################################################################### ### 5. CROSS-USER SKILL QUERIES (Protected Routes) +### NOTE: category parameter is REQUIRED for multi-key GSI queries ############################################################################### -### 5.1 Find All Users with Python -GET {{API_URL}}/skills/Python/users +### 5.1 Find All Users with Python (Programming category) +GET {{API_URL}}/skills/Python/users?category=Programming Authorization: Bearer {{token}} -### 5.2 Find All Users with JavaScript -GET {{API_URL}}/skills/JavaScript/users +### 5.2 Find All Users with JavaScript (Programming category) +GET {{API_URL}}/skills/JavaScript/users?category=Programming Authorization: Bearer {{token}} -### 5.3 Find All Users with Go -GET {{API_URL}}/skills/Go/users +### 5.3 Find All Users with Go (Programming category) +GET {{API_URL}}/skills/Go/users?category=Programming Authorization: Bearer {{token}} ### 5.4 Find Expert Python Developers -GET {{API_URL}}/skills/Python/users?level=Expert +GET {{API_URL}}/skills/Python/users?category=Programming&level=Expert Authorization: Bearer {{token}} ### 5.5 Find Intermediate Python Developers -GET {{API_URL}}/skills/Python/users?level=Intermediate +GET {{API_URL}}/skills/Python/users?category=Programming&level=Intermediate Authorization: Bearer {{token}} ### 5.6 Find Advanced Python Developers -GET {{API_URL}}/skills/Python/users?level=Advanced +GET {{API_URL}}/skills/Python/users?category=Programming&level=Advanced Authorization: Bearer {{token}} ### 5.7 Find Beginner Python Developers -GET {{API_URL}}/skills/Python/users?level=Beginner +GET {{API_URL}}/skills/Python/users?category=Programming&level=Beginner Authorization: Bearer {{token}} ### 5.8 Find Expert JavaScript Developers -GET {{API_URL}}/skills/JavaScript/users?level=Expert +GET {{API_URL}}/skills/JavaScript/users?category=Programming&level=Expert Authorization: Bearer {{token}} ### 5.9 Find Advanced JavaScript Developers -GET {{API_URL}}/skills/JavaScript/users?level=Advanced +GET {{API_URL}}/skills/JavaScript/users?category=Programming&level=Advanced Authorization: Bearer {{token}} ### 5.10 Find Expert Go Developers -GET {{API_URL}}/skills/Go/users?level=Expert +GET {{API_URL}}/skills/Go/users?category=Programming&level=Expert Authorization: Bearer {{token}} ### 5.11 Find Intermediate Go Developers -GET {{API_URL}}/skills/Go/users?level=Intermediate +GET {{API_URL}}/skills/Go/users?category=Programming&level=Intermediate Authorization: Bearer {{token}} ############################################################################### @@ -703,8 +704,8 @@ Content-Type: application/json "notes": "Making good progress with Rust" } -### 8.8 WORKFLOW Step 8 - Find All Rust Developers -GET {{API_URL}}/skills/Rust/users +### 8.8 WORKFLOW Step 8 - Find All Rust Developers (Programming category) +GET {{API_URL}}/skills/Rust/users?category=Programming Authorization: Bearer {{token}} ### 8.9 WORKFLOW Step 9 - Update User Profile diff --git a/docs/dynamodb_table_design.md b/docs/dynamodb_table_design.md new file mode 100644 index 0000000..a2ea66b --- /dev/null +++ b/docs/dynamodb_table_design.md @@ -0,0 +1,1044 @@ +# DynamoDB Test Queries - AWS CLI Commands + +This document contains executable AWS CLI commands to test all query patterns for the GLAD entities table. + +--- + +## Table Overview + +### Main Table: `glad-entities` + +**Table Structure (from CDK):** +- **Partition Key (PK):** `EntityType` (String) - Entity type discriminator +- **Sort Key (SK):** `entity_id` (String) - Unique identifier for each entity +- **Billing Mode:** PAY_PER_REQUEST (on-demand) +- **Point-in-Time Recovery:** Disabled +- **Deletion Protection:** Enabled + +**Entity Types:** +- `User` - User profiles +- `Skill` - Master skill definitions (catalog) +- `UserSkill` - User-to-skill relationships with proficiency data + +--- + +## The Power of Multi-Key Composite GSI + +**With just ONE Global Secondary Index using composite keys, you can create 15+ different query patterns!** + +This is the power of DynamoDB's multi-key GSI design. Instead of creating multiple indexes for different query patterns, you strategically design a single index with composite keys that enables flexible querying at multiple levels of granularity. + +### Single GSI: `BySkill` + +**Composite Partition Key (1 attribute):** +- `Category` (String) - Skill category for broad partitioning + +**Composite Sort Keys (4 attributes):** +1. `SkillName` (String) - Specific skill name +2. `ProficiencyLevel` (String) - Skill proficiency level +3. `YearsOfExperience` (Number) - Years of experience with the skill +4. `Username` (String) - User identifier (ensures uniqueness) + +**Why This Design is Powerful:** + +✅ **Hierarchical Querying:** Query from broad (Category) to specific (down to individual Username) +✅ **Partial Sort Keys:** Use just SK1, or SK1+SK2, or SK1+SK2+SK3, etc. - DynamoDB allows left-to-right querying +✅ **Range Operations:** Apply `>=`, `<=`, `BETWEEN` on the last sort key in your query +✅ **Efficient Sorting:** Data is automatically sorted by the composite key order +✅ **Single Index Cost:** Pay for only one GSI instead of multiple indexes + +**Query Flexibility Examples:** +- Query 1: `Category = "Programming"` → All programming skills +- Query 2: `Category = "Programming" AND SkillName = "Python"` → All Python users +- Query 3: `Category = "Programming" AND SkillName = "Python" AND ProficiencyLevel = "Expert"` → Python experts +- Query 4: `... AND YearsOfExperience >= 5` → Experienced Python experts +- Query 5: `... AND Username = "john"` → Specific user check + +**All from ONE index!** + +--- + +## Sample Data Structure + +### Main Table Sample Items + +| EntityType | entity_id | Additional Attributes | Description | +|-------------|-----------------------------|---------------------------------------------------------------------------------------------------------|-------------------------------| +| `User` | `USER#john_doe` | Username, Name, Email, CreatedAt, UpdatedAt | User profile | +| `Skill` | `SKILL#python` | SkillID, SkillName, Category, Description, Tags | Master skill catalog | +| `UserSkill` | `USERSKILL#john_doe#python` | Username, SkillID, SkillName, Category, ProficiencyLevel, YearsOfExperience, Endorsements, LastUsedDate | User's skill with proficiency | + +### GSI `BySkill` Sample Items + +Here's how UserSkill items appear in the GSI (sorted by composite sort key): + +| Category | SkillName | ProficiencyLevel | YearsOfExperience | Username | Endorsements | LastUsedDate | +|-------------|------------|------------------|-------------------|---------------|--------------|--------------| +| Programming | Python | Beginner | 1 | jane_smith | 5 | 2025-12-10 | +| Programming | Python | Intermediate | 3 | mike_wilson | 25 | 2025-11-15 | +| Programming | Python | Advanced | 5 | alice_johnson | 65 | 2025-12-15 | +| Programming | Python | Expert | 7 | bob_smith | 120 | 2025-12-18 | +| Programming | Python | Expert | 10 | diana_evans | 200 | 2025-12-20 | +| Frontend | TypeScript | Advanced | 4 | alex_chen | 30 | 2025-12-15 | +| Frontend | TypeScript | Expert | 6 | betty_wang | 75 | 2025-12-18 | +| Backend | Go | Expert | 7 | alice_smith | 80 | 2025-12-18 | +| Cloud | AWS | Expert | 9 | charlie_brown | 150 | 2025-11-20 | +| DevOps | Docker | Beginner | 1 | tom_davis | 8 | 2025-10-10 | + +**Notice:** Items are naturally sorted by `Category` → `SkillName` → `ProficiencyLevel` → `YearsOfExperience` → `Username`. This sorting enables efficient range queries and pagination. + +--- + +## Key Attributes Explained + +### Main Table Keys +- **EntityType:** Discriminator to separate Users, Skills, and UserSkills +- **entity_id:** Hierarchical identifier pattern: + - `USER#` + - `SKILL#` + - `USERSKILL##` + +### GSI Keys (BySkill) +- **Category (PK):** Broad partitioning (Programming, Frontend, Backend, Cloud, DevOps, Database, Mobile, Data, Security, Other) +- **SkillName (SK1):** Specific skill within category +- **ProficiencyLevel (SK2):** Beginner, Intermediate, Advanced, Expert +- **YearsOfExperience (SK3):** NUMBER type for range queries and sorting +- **Username (SK4):** User identifier, ensures uniqueness + +### Non-Key Attributes (Available for FilterExpression) +- **Endorsements:** NUMBER - Peer endorsements count +- **LastUsedDate:** STRING (ISO 8601) - When skill was last used +- **Notes:** STRING - Additional skill notes +- **SkillID:** STRING - Immutable skill identifier +- **CreatedAt/UpdatedAt:** STRING (ISO 8601) - Timestamps + +--- + +## GSI Design (Option 1 - Maximum Flexibility) + +**Index Name:** `BySkill` + +**Partition Key (1 attribute):** +- `Category` (String) + +**Sort Keys (4 attributes):** +1. `SkillName` (String) +2. `ProficiencyLevel` (String) +3. `YearsOfExperience` (Number) +4. `Username` (String) + +**Note:** Endorsements (Number) and LastUsedDate (String) are not in the GSI keys, but can be used with FilterExpression. + +--- + +## Access Patterns Summary + +### Main Table Access Patterns + +| # | Pattern Name | Table/Index | Key Condition | Use Case | API Endpoint | +|---|---------------------------|-------------|--------------------------------------------------------------------------------|-----------------------------------|--------------------------------------------| +| 1 | Get All Users | Main Table | `EntityType = "User"` | List all users in system | `GET /users` | +| 2 | Get All Master Skills | Main Table | `EntityType = "Skill"` | List all master skill definitions | `GET /master-skills` | +| 3 | Get All User Skills | Main Table | `EntityType = "UserSkill"` | List all user skill records | - | +| 4 | Get Specific User | Main Table | `EntityType = "User" AND entity_id = "USER#"` | Get user profile by username | `GET /users/{username}` | +| 5 | Get Specific Master Skill | Main Table | `EntityType = "Skill" AND entity_id = "SKILL#"` | Get master skill details | `GET /master-skills/{skillID}` | +| 6 | Get Specific User Skill | Main Table | `EntityType = "UserSkill" AND entity_id = "USERSKILL##"` | Get user's specific skill | `GET /users/{username}/skills/{skillName}` | +| 7 | Get All Skills for User | Main Table | `EntityType = "UserSkill" AND begins_with(entity_id, "USERSKILL##")` | List all skills for a user | `GET /users/{username}/skills` | + +### GSI Access Patterns (BySkill Index) + +| # | Pattern Name | PK | SK Condition | Filter | Use Case | API Endpoint | +|----|-------------------------------|-------------------|------------------------------------------------------------------------------------------------|--------------------------------------------------|---------------------------------------------------------------------|----------------------------------------------------------------------------------------------| +| 8 | All Skills in Category | `Category = :cat` | - | - | All skills in Programming category | `GET /skills?category=Programming` | +| 9 | All Users with Skill | `Category = :cat` | `SkillName = :skill` | - | Everyone who knows Python | `GET /skills/python/users` | +| 10 | Users at Skill Level | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level` | - | All Python experts | `GET /skills/python/users?level=Expert` | +| 11 | Users with Min Experience | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level AND YearsOfExperience >= :years` | - | Python experts with 5+ years | `GET /skills/python/users?level=Expert&minYears=5` | +| 12 | Users by Experience (Desc) | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level` | `--scan-index-forward false` | Most experienced Python experts | `GET /skills/python/users?level=Expert&sort=experience_desc` | +| 13 | Experience Range | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level AND YearsOfExperience BETWEEN :min AND :max` | - | Intermediate Python devs (2-5 years) | `GET /skills/python/users?level=Intermediate&minYears=2&maxYears=5` | +| 14 | Check User Skill Level | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level AND Username = :user` | - | Check if john_doe is Python expert | `GET /users/john_doe/skills/python?checkLevel=Expert` | +| 15 | Filter by Endorsements | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level` | `Endorsements >= :min` | Python experts with 50+ endorsements | `GET /skills/python/users?level=Expert&minEndorsements=50` | +| 16 | Filter by Last Used | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level` | `LastUsedDate >= :date` | Python experts active in last 6 months | `GET /skills/python/users?level=Expert&activeSince=2024-06-20` | +| 17 | Multi-Criteria Filter | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level AND YearsOfExperience >= :years` | `Endorsements >= :min AND LastUsedDate >= :date` | Senior Python experts (5+ years, 50+ endorsements, recently active) | `GET /skills/python/users?level=Expert&minYears=5&minEndorsements=50&activeSince=2024-06-20` | +| 18 | All Expert Skills in Category | `Category = :cat` | - | `ProficiencyLevel = :level` | All expert-level skills in Programming | `GET /skills?category=Programming&level=Expert` | +| 19 | Skills with Prefix | `Category = :cat` | `begins_with(SkillName, :prefix)` | - | All Java-related skills (Java, JavaScript) | `GET /skills?category=Programming&skillPrefix=Java` | +| 20 | Proficiency Distribution | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level` | `--select COUNT` | Count Python users by proficiency level | `GET /skills/python/distribution` | +| 21 | Top N by Experience | `Category = :cat` | `SkillName = :skill AND ProficiencyLevel = :level` | `--scan-index-forward false --limit N` | Top 10 most experienced Go experts | `GET /skills/go/users/top?limit=10&level=Expert` | +| 22 | Users Above Min Level | `Category = :cat` | `SkillName = :skill` | `ProficiencyLevel IN (:level1, :level2)` | TypeScript users at Advanced or Expert | `GET /skills/typescript/users?minLevel=Advanced` | +| 23 | Pagination | `Category = :cat` | - | `--limit N --exclusive-start-key` | Paginated results for category | `GET /skills?category=Programming&page=2&limit=20` | + +**Total Access Patterns:** 23 (7 Main Table + 16 GSI) + +--- + +## Prerequisites + +```bash +# AWS Profile +export AWS_PROFILE=passbrains-ilisa-amplify + +# Table and Index names +TABLE_NAME="glad-entities" +GSI_NAME="BySkill" +``` + +--- + +## Main Table Query Patterns + +### Pattern 1: Get All Users + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType" \ + --expression-attribute-values '{ + ":entityType": {"S": "User"} + }' +``` + +--- + +### Pattern 2: Get All Master Skills + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType" \ + --expression-attribute-values '{ + ":entityType": {"S": "Skill"} + }' +``` + +--- + +### Pattern 3: Get All User Skills + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType" \ + --expression-attribute-values '{ + ":entityType": {"S": "UserSkill"} + }' +``` + +--- + +### Pattern 4: Get Specific User + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ + --expression-attribute-values '{ + ":entityType": {"S": "User"}, + ":entityId": {"S": "USER#john_doe"} + }' +``` + +--- + +### Pattern 5: Get Specific Master Skill + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ + --expression-attribute-values '{ + ":entityType": {"S": "Skill"}, + ":entityId": {"S": "SKILL#python"} + }' +``` + +--- + +### Pattern 6: Get Specific User Skill + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ + --expression-attribute-values '{ + ":entityType": {"S": "UserSkill"}, + ":entityId": {"S": "USERSKILL#jane_doe#python"} + }' +``` + +--- + +### Pattern 7: Get All Skills for a User + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType AND begins_with(entity_id, :prefix)" \ + --expression-attribute-values '{ + ":entityType": {"S": "UserSkill"}, + ":prefix": {"S": "USERSKILL#john_doe#"} + }' +``` + +--- + +## GSI Query Patterns (BySkill) - Maximum Flexibility! + +### GSI Pattern 1: All Skills in a Category + +**Use Case:** List all user skills in a specific category (e.g., all Programming skills) + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category" \ + --expression-attribute-values '{ + ":category": {"S": "Programming"} + }' +``` + +**Returns:** All UserSkill items in the Programming category + +--- + +### GSI Pattern 2: All Users with a Specific Skill (Any Level) + +**Use Case:** Find everyone who has Python, regardless of proficiency level + +//todo +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName" \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":skillName": {"S": "JavaScript"} + }' +``` + +**Returns:** All UserSkill items for Python (all proficiency levels) + +--- + +### GSI Pattern 3: Users with Skill at Specific Proficiency Level + +**Use Case:** Find all Python experts + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --expression-attribute-values '{ + ":category": {"S": "DevOps"}, + ":skillName": {"S": "Docker"}, + ":level": {"S": "Beginner"} + }' +``` + +**Returns:** All Python experts + +--- + +### GSI Pattern 4: Users with Minimum Years of Experience + +**Use Case:** Find Python experts with at least 5 years of experience + +**Option A: Using Sort Key Condition (Efficient)** + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level AND YearsOfExperience >= :minYears" \ + --expression-attribute-values '{ + ":category": {"S": "DevOps"}, + ":skillName": {"S": "Docker"}, + ":level": {"S": "Beginner"}, + ":minYears": {"N": "1"} + }' +``` + +**Returns:** Python experts with 5+ years of experience, sorted by experience (ascending) + +--- + +### GSI Pattern 5: Users Sorted by Experience (Descending) + +**Use Case:** Find Python experts ordered by most experienced first + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --no-scan-index-forward \ + --expression-attribute-values '{ + ":category": {"S": "DevOps"}, + ":skillName": {"S": "Docker"}, + ":level": {"S": "Beginner"} + }' +``` + +**Returns:** Python experts sorted by experience (most experienced first) + +--- + +### GSI Pattern 6: Experience Range Query + +**Use Case:** Find intermediate Python developers with 2-5 years of experience + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level AND YearsOfExperience BETWEEN :minYears AND :maxYears" \ + --expression-attribute-values '{ + ":category": {"S": "DevOps"}, + ":skillName": {"S": "Docker"}, + ":level": {"S": "Beginner"}, + ":minYears": {"N": "1"}, + ":maxYears": {"N": "10"} + }' +``` + +**Returns:** Intermediate Python developers with 2-5 years experience + +--- + +### GSI Pattern 7: Check if Specific User Has Skill at Level + +**Use Case:** Check if user "john_doe" is a Python expert + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ + --expression-attribute-values '{ + ":entityType": {"S": "UserSkill"}, + ":entityId": {"S": "USERSKILL#jane_doe#python"} + }' + +``` + +**Returns:** Single UserSkill item if exists, empty if not + +--- + +### GSI Pattern 8: Filter by Endorsements (Using FilterExpression) + +**Use Case:** Find Python experts with at least 50 endorsements + +**Note:** Endorsements is NOT in the GSI keys, so we use FilterExpression + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --filter-expression "Endorsements >= :minEndorsements" \ + --expression-attribute-values '{ + ":category": {"S": "DevOps"}, + ":skillName": {"S": "Docker"}, + ":level": {"S": "Beginner"}, + ":minEndorsements": {"N": "2"} + }' +``` + +**Returns:** Python experts with 50+ endorsements (filtered after query) + +--- + +### GSI Pattern 9: Filter by Last Used Date (Using FilterExpression) + +**Use Case:** Find Beginners who used Docker in the last 6 months + +**Note:** LastUsedDate is NOT in the GSI keys, so we use FilterExpression + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --filter-expression "LastUsedDate >= :recentDate" \ + --expression-attribute-values '{ + ":category": {"S": "DevOps"}, + ":skillName": {"S": "Docker"}, + ":level": {"S": "Beginner"}, + ":recentDate": {"S": "2024-06-20"} + }' +``` + +**Returns:** Beginners who used Docker since 2024-06-20 + +--- + +### GSI Pattern 10: Complex Multi-Criteria Filter + +**Use Case:** Find senior Python experts (5+ years, 50+ endorsements, used recently) + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level AND YearsOfExperience >= :minYears" \ + --filter-expression "Endorsements >= :minEndorsements AND LastUsedDate >= :minDate" \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":skillName": {"S": "Python 3"}, + ":level": {"S": "Expert"}, + ":minYears": {"N": "5"}, + ":minEndorsements": {"N": "50"}, + ":minDate": {"S": "2024-06-20"} + }' +``` + +**Returns:** Highly qualified, active Python experts + +--- + +### GSI Pattern 11: All Skills at Specific Proficiency (Across All Skills) + +**Use Case:** Find all Expert-level skills in Programming category + +**⚠️ WARNING:** This query will FAIL because `ProficiencyLevel` is a GSI key attribute (SK2) and cannot be used in FilterExpression. + +**Error:** `Filter Expression can only contain non-primary key attributes: Primary key attribute: ProficiencyLevel` + +**Workaround:** Query each proficiency level separately or fetch all skills in the category and filter in application code. + +```bash +# This query will return an ERROR - kept for reference +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category" \ + --filter-expression "ProficiencyLevel = :level" \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":level": {"S": "Expert"} + }' +``` + +**Alternative - Fetch all and filter in code:** +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category" \ + --expression-attribute-values '{ + ":category": {"S": "Programming"} + }' +# Then filter ProficiencyLevel = "Expert" in your application code +``` + +--- + +### GSI Pattern 12: Skills Starting with Prefix + +**Use Case:** Find all users with skills starting with "Java" (Java, JavaScript, etc.) + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND begins_with(SkillName, :skillPrefix)" \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":skillPrefix": {"S": "Java"} + }' +``` + +**Returns:** All UserSkills with skills starting with "Java" + +--- + +### GSI Pattern 13: Proficiency Distribution for a Skill (Count by Level) + +**Use Case:** Get count of Python users at each proficiency level + +**Query 1 - Beginner:** + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --select COUNT \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":skillName": {"S": "Python"}, + ":level": {"S": "Beginner"} + }' +``` + +**Query 2 - Intermediate:** + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --select COUNT \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":skillName": {"S": "Python"}, + ":level": {"S": "Intermediate"} + }' +``` + +**Query 3 - Advanced:** + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --select COUNT \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":skillName": {"S": "Python"}, + ":level": {"S": "Advanced"} + }' +``` + +**Query 4 - Expert:** + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --select COUNT \ + --expression-attribute-values '{ + ":category": {"S": "DevOps"}, + ":skillName": {"S": "Docker"}, + ":level": {"S": "Beginner"} + }' +``` + +**Returns:** Count for each proficiency level (4 queries total) + +--- + +### GSI Pattern 14: Top N Users by Experience + +**Use Case:** Get top 10 most experienced Go experts + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --limit 10 \ + --expression-attribute-values '{ + ":category": {"S": "Backend"}, + ":skillName": {"S": "Go"}, + ":level": {"S": "Expert"} + }' +``` + +**Returns:** Top 10 Go experts by experience + +--- + +### GSI Pattern 15: Users Above Minimum Level (Multiple Levels) + +**Use Case:** Find TypeScript users at Advanced OR Expert level + +**Note:** This pattern returns ALL proficiency levels, so filter in application code or run separate queries for each level. + +```bash + aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName" \ + --expression-attribute-values '{ + ":category": {"S": "Frontend"}, + ":skillName": {"S": "TypeScript"} + }' + +# Then filter for ProficiencyLevel IN ("Advanced", "Expert") in application code +``` + +**Alternative - Run two separate queries:** +```bash +# Query 1: Advanced +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --expression-attribute-values '{ + ":category": {"S": "Frontend"}, + ":skillName": {"S": "TypeScript"}, + ":level": {"S": "Advanced"} + }' + +# Query 2: Expert +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ + --expression-attribute-values '{ + ":category": {"S": "Frontend"}, + ":skillName": {"S": "TypeScript"}, + ":level": {"S": "Expert"} + }' +# Merge results in application code +``` + +**Returns:** TypeScript users at Advanced or Expert level + +--- + +### GSI Pattern 16: Pagination Example + +**Use Case:** Get first 20 skills in Programming category, then get next page + +**Page 1:** + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category" \ + --limit 20 \ + --expression-attribute-values '{ + ":category": {"S": "Programming"} + }' +``` + +**Page 2 (use LastEvaluatedKey from previous response):** + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category" \ + --limit 20 \ + --exclusive-start-key '{"Category":{"S":"Programming"},"SkillName":{"S":"Python"},"ProficiencyLevel":{"S":"Expert"},"YearsOfExperience":{"N":"5"},"Username":{"S":"alice"},"EntityType":{"S":"UserSkill"},"entity_id":{"S":"USERSKILL#alice#python"}}' \ + --expression-attribute-values '{ + ":category": {"S": "Programming"} + }' +``` + +--- + +## Utility Commands + +### Count Total Users + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key-condition-expression "EntityType = :entityType" \ + --select COUNT \ + --expression-attribute-values '{ + ":entityType": {"S": "User"} + }' +``` + +--- + +### Count Users with Specific Skill + +```bash +aws dynamodb query \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --index-name BySkill \ + --key-condition-expression "Category = :category AND SkillName = :skillName" \ + --select COUNT \ + --expression-attribute-values '{ + ":category": {"S": "Programming"}, + ":skillName": {"S": "Python"} + }' +``` + +--- + +### Get Item by Primary Key (GetItem - faster than Query) + +```bash +aws dynamodb get-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --key '{ + "EntityType": {"S": "User"}, + "entity_id": {"S": "USER#john_doe"} + }' +``` + +--- + +## Sample Data for Testing + +### Create Test Users + +```bash +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "User"}, + "entity_id": {"S": "USER#john_doe"}, + "Username": {"S": "john_doe"}, + "Name": {"S": "John Doe"}, + "Email": {"S": "john@example.com"}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-01-01T00:00:00Z"} + }' + +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "User"}, + "entity_id": {"S": "USER#jane_doe"}, + "Username": {"S": "jane_doe"}, + "Name": {"S": "Jane Doe"}, + "Email": {"S": "jane@example.com"}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-01-01T00:00:00Z"} + }' + +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "User"}, + "entity_id": {"S": "USER#alice_smith"}, + "Username": {"S": "alice_smith"}, + "Name": {"S": "Alice Smith"}, + "Email": {"S": "alice@example.com"}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-01-01T00:00:00Z"} + }' +``` + +--- + +### Create Test Master Skills + +```bash +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "Skill"}, + "entity_id": {"S": "SKILL#python"}, + "SkillID": {"S": "python"}, + "SkillName": {"S": "Python"}, + "Category": {"S": "Programming"}, + "Description": {"S": "Python programming language"}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-01-01T00:00:00Z"} + }' + +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "Skill"}, + "entity_id": {"S": "SKILL#go"}, + "SkillID": {"S": "go"}, + "SkillName": {"S": "Go"}, + "Category": {"S": "Backend"}, + "Description": {"S": "Go programming language"}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-01-01T00:00:00Z"} + }' + +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "Skill"}, + "entity_id": {"S": "SKILL#react"}, + "SkillID": {"S": "react"}, + "SkillName": {"S": "React"}, + "Category": {"S": "Frontend"}, + "Description": {"S": "React JavaScript library"}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-01-01T00:00:00Z"} + }' +``` + +--- + +### Create Test UserSkills + +```bash +# John - Python Expert (10 years, 150 endorsements) +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "UserSkill"}, + "entity_id": {"S": "USERSKILL#john_doe#python"}, + "Username": {"S": "john_doe"}, + "SkillID": {"S": "python"}, + "SkillName": {"S": "Python"}, + "Category": {"S": "Programming"}, + "ProficiencyLevel": {"S": "Expert"}, + "YearsOfExperience": {"N": "10"}, + "Endorsements": {"N": "150"}, + "LastUsedDate": {"S": "2025-12-15"}, + "Notes": {"S": "Experienced Python developer"}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-12-15T00:00:00Z"} + }' + +# Jane - Python Advanced (5 years, 42 endorsements) +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "UserSkill"}, + "entity_id": {"S": "USERSKILL#jane_doe#python"}, + "Username": {"S": "jane_doe"}, + "SkillID": {"S": "python"}, + "SkillName": {"S": "Python"}, + "Category": {"S": "Programming"}, + "ProficiencyLevel": {"S": "Advanced"}, + "YearsOfExperience": {"N": "5"}, + "Endorsements": {"N": "42"}, + "LastUsedDate": {"S": "2025-11-20"}, + "Notes": {"S": ""}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-11-20T00:00:00Z"} + }' + +# Alice - Go Expert (7 years, 80 endorsements) +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "UserSkill"}, + "entity_id": {"S": "USERSKILL#alice_smith#go"}, + "Username": {"S": "alice_smith"}, + "SkillID": {"S": "go"}, + "SkillName": {"S": "Go"}, + "Category": {"S": "Backend"}, + "ProficiencyLevel": {"S": "Expert"}, + "YearsOfExperience": {"N": "7"}, + "Endorsements": {"N": "80"}, + "LastUsedDate": {"S": "2025-12-18"}, + "Notes": {"S": ""}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-12-18T00:00:00Z"} + }' + +# John - React Intermediate (3 years, 25 endorsements) +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "UserSkill"}, + "entity_id": {"S": "USERSKILL#john_doe#react"}, + "Username": {"S": "john_doe"}, + "SkillID": {"S": "react"}, + "SkillName": {"S": "React"}, + "Category": {"S": "Frontend"}, + "ProficiencyLevel": {"S": "Intermediate"}, + "YearsOfExperience": {"N": "3"}, + "Endorsements": {"N": "25"}, + "LastUsedDate": {"S": "2025-12-10"}, + "Notes": {"S": ""}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-12-10T00:00:00Z"} + }' + +# Jane - Python Beginner (1 year, 5 endorsements) +aws dynamodb put-item \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --item '{ + "EntityType": {"S": "UserSkill"}, + "entity_id": {"S": "USERSKILL#jane_doe#go"}, + "Username": {"S": "jane_doe"}, + "SkillID": {"S": "go"}, + "SkillName": {"S": "Go"}, + "Category": {"S": "Backend"}, + "ProficiencyLevel": {"S": "Beginner"}, + "YearsOfExperience": {"N": "1"}, + "Endorsements": {"N": "5"}, + "LastUsedDate": {"S": "2025-10-15"}, + "Notes": {"S": ""}, + "CreatedAt": {"S": "2025-01-01T00:00:00Z"}, + "UpdatedAt": {"S": "2025-10-15T00:00:00Z"} + }' +``` + +--- + +## Troubleshooting + +### Check if GSI Exists + +```bash +aws dynamodb describe-table \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --query "Table.GlobalSecondaryIndexes[?IndexName=='BySkill']" +``` + +--- + +### Check GSI Status + +```bash +aws dynamodb describe-table \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --query "Table.GlobalSecondaryIndexes[?IndexName=='BySkill'].IndexStatus" +``` + +--- + +### List All Attributes in Table + +```bash +aws dynamodb describe-table \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --query "Table.AttributeDefinitions" +``` + +--- + +### Scan Entire Table (Use Sparingly!) + +```bash +aws dynamodb scan \ + --table-name glad-entities --profile passbrains-ilisa-amplify \ + --limit 10 +``` + +--- + +## Query Pattern Summary + +| Pattern | PK | SK1 | SK2 | SK3 | SK4 | Use Case | +|---------|----------|-----------|------------------|---------------------------|----------|------------------------| +| 1 | Category | - | - | - | - | All skills in category | +| 2 | Category | SkillName | - | - | - | All users with skill | +| 3 | Category | SkillName | ProficiencyLevel | - | - | Users at skill level | +| 4 | Category | SkillName | ProficiencyLevel | YearsOfExperience >= | - | Min experience filter | +| 5 | Category | SkillName | ProficiencyLevel | YearsOfExperience (desc) | - | Most experienced first | +| 6 | Category | SkillName | ProficiencyLevel | YearsOfExperience BETWEEN | - | Experience range | +| 7 | Category | SkillName | ProficiencyLevel | YearsOfExperience | Username | Specific user check | + +--- + +## Important Notes + +1. **Data Types:** + - `YearsOfExperience`: NUMBER type (e.g., {"N": "5"}) + - `Endorsements`: NUMBER type (e.g., {"N": "42"}) + - No zero-padding needed since both are stored as numbers + +2. **Sort Key Hierarchy:** + - Must query from left to right + - Can use partial sort keys (SK1, SK1+SK2, SK1+SK2+SK3, etc.) + - Cannot skip sort keys (e.g., cannot query SK1+SK3 without SK2) + +3. **Range Queries:** + - Only on the LAST sort key specified + - Operators: `<`, `>`, `<=`, `>=`, `BETWEEN`, `begins_with` + +4. **FilterExpression:** + - For attributes NOT in GSI keys (Endorsements, LastUsedDate) + - Applied AFTER query (less efficient than KeyConditionExpression) + +5. **Sorting:** + - Default: Ascending (`--scan-index-forward true`) + - Descending: `--scan-index-forward false` + +6. **Pagination:** + - Use `--limit` to control page size + - Use `--exclusive-start-key` with LastEvaluatedKey for next page \ No newline at end of file From 80929ba7d9bc8af113546716b056f20c32590028 Mon Sep 17 00:00:00 2001 From: Alex Ilies Date: Sat, 20 Dec 2025 23:14:38 +0200 Subject: [PATCH 2/2] infra(gsi): gsi changes --- docs/dynamodb_table_design.md | 98 +++++++++++++++++------------------ 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/dynamodb_table_design.md b/docs/dynamodb_table_design.md index a2ea66b..221b314 100644 --- a/docs/dynamodb_table_design.md +++ b/docs/dynamodb_table_design.md @@ -174,7 +174,7 @@ Here's how UserSkill items appear in the GSI (sorted by composite sort key): ```bash # AWS Profile -export AWS_PROFILE=passbrains-ilisa-amplify +export AWS_PROFILE={YOUR_AWS_PROFILE_NAME} # Table and Index names TABLE_NAME="glad-entities" @@ -189,7 +189,7 @@ GSI_NAME="BySkill" ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType" \ --expression-attribute-values '{ ":entityType": {"S": "User"} @@ -202,7 +202,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType" \ --expression-attribute-values '{ ":entityType": {"S": "Skill"} @@ -215,7 +215,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType" \ --expression-attribute-values '{ ":entityType": {"S": "UserSkill"} @@ -228,7 +228,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ --expression-attribute-values '{ ":entityType": {"S": "User"}, @@ -242,7 +242,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ --expression-attribute-values '{ ":entityType": {"S": "Skill"}, @@ -256,7 +256,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ --expression-attribute-values '{ ":entityType": {"S": "UserSkill"}, @@ -270,7 +270,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType AND begins_with(entity_id, :prefix)" \ --expression-attribute-values '{ ":entityType": {"S": "UserSkill"}, @@ -288,7 +288,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category" \ --expression-attribute-values '{ @@ -307,7 +307,7 @@ aws dynamodb query \ //todo ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName" \ --expression-attribute-values '{ @@ -326,7 +326,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --expression-attribute-values '{ @@ -348,7 +348,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level AND YearsOfExperience >= :minYears" \ --expression-attribute-values '{ @@ -369,7 +369,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --no-scan-index-forward \ @@ -390,7 +390,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level AND YearsOfExperience BETWEEN :minYears AND :maxYears" \ --expression-attribute-values '{ @@ -412,7 +412,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType AND entity_id = :entityId" \ --expression-attribute-values '{ ":entityType": {"S": "UserSkill"}, @@ -433,7 +433,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --filter-expression "Endorsements >= :minEndorsements" \ @@ -457,7 +457,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --filter-expression "LastUsedDate >= :recentDate" \ @@ -479,7 +479,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level AND YearsOfExperience >= :minYears" \ --filter-expression "Endorsements >= :minEndorsements AND LastUsedDate >= :minDate" \ @@ -510,7 +510,7 @@ aws dynamodb query \ ```bash # This query will return an ERROR - kept for reference aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category" \ --filter-expression "ProficiencyLevel = :level" \ @@ -523,7 +523,7 @@ aws dynamodb query \ **Alternative - Fetch all and filter in code:** ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category" \ --expression-attribute-values '{ @@ -540,7 +540,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND begins_with(SkillName, :skillPrefix)" \ --expression-attribute-values '{ @@ -561,7 +561,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --select COUNT \ @@ -576,7 +576,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --select COUNT \ @@ -591,7 +591,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --select COUNT \ @@ -606,7 +606,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --select COUNT \ @@ -627,7 +627,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --limit 10 \ @@ -650,7 +650,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName" \ --expression-attribute-values '{ @@ -665,7 +665,7 @@ aws dynamodb query \ ```bash # Query 1: Advanced aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --expression-attribute-values '{ @@ -676,7 +676,7 @@ aws dynamodb query \ # Query 2: Expert aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName AND ProficiencyLevel = :level" \ --expression-attribute-values '{ @@ -699,7 +699,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category" \ --limit 20 \ @@ -712,7 +712,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category" \ --limit 20 \ @@ -730,7 +730,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key-condition-expression "EntityType = :entityType" \ --select COUNT \ --expression-attribute-values '{ @@ -744,7 +744,7 @@ aws dynamodb query \ ```bash aws dynamodb query \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --index-name BySkill \ --key-condition-expression "Category = :category AND SkillName = :skillName" \ --select COUNT \ @@ -760,7 +760,7 @@ aws dynamodb query \ ```bash aws dynamodb get-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --key '{ "EntityType": {"S": "User"}, "entity_id": {"S": "USER#john_doe"} @@ -775,7 +775,7 @@ aws dynamodb get-item \ ```bash aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "User"}, "entity_id": {"S": "USER#john_doe"}, @@ -787,7 +787,7 @@ aws dynamodb put-item \ }' aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "User"}, "entity_id": {"S": "USER#jane_doe"}, @@ -799,7 +799,7 @@ aws dynamodb put-item \ }' aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "User"}, "entity_id": {"S": "USER#alice_smith"}, @@ -817,7 +817,7 @@ aws dynamodb put-item \ ```bash aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "Skill"}, "entity_id": {"S": "SKILL#python"}, @@ -830,7 +830,7 @@ aws dynamodb put-item \ }' aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "Skill"}, "entity_id": {"S": "SKILL#go"}, @@ -843,7 +843,7 @@ aws dynamodb put-item \ }' aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "Skill"}, "entity_id": {"S": "SKILL#react"}, @@ -863,7 +863,7 @@ aws dynamodb put-item \ ```bash # John - Python Expert (10 years, 150 endorsements) aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "UserSkill"}, "entity_id": {"S": "USERSKILL#john_doe#python"}, @@ -882,7 +882,7 @@ aws dynamodb put-item \ # Jane - Python Advanced (5 years, 42 endorsements) aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "UserSkill"}, "entity_id": {"S": "USERSKILL#jane_doe#python"}, @@ -901,7 +901,7 @@ aws dynamodb put-item \ # Alice - Go Expert (7 years, 80 endorsements) aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "UserSkill"}, "entity_id": {"S": "USERSKILL#alice_smith#go"}, @@ -920,7 +920,7 @@ aws dynamodb put-item \ # John - React Intermediate (3 years, 25 endorsements) aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "UserSkill"}, "entity_id": {"S": "USERSKILL#john_doe#react"}, @@ -939,7 +939,7 @@ aws dynamodb put-item \ # Jane - Python Beginner (1 year, 5 endorsements) aws dynamodb put-item \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --item '{ "EntityType": {"S": "UserSkill"}, "entity_id": {"S": "USERSKILL#jane_doe#go"}, @@ -965,7 +965,7 @@ aws dynamodb put-item \ ```bash aws dynamodb describe-table \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --query "Table.GlobalSecondaryIndexes[?IndexName=='BySkill']" ``` @@ -975,7 +975,7 @@ aws dynamodb describe-table \ ```bash aws dynamodb describe-table \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --query "Table.GlobalSecondaryIndexes[?IndexName=='BySkill'].IndexStatus" ``` @@ -985,7 +985,7 @@ aws dynamodb describe-table \ ```bash aws dynamodb describe-table \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --query "Table.AttributeDefinitions" ``` @@ -995,7 +995,7 @@ aws dynamodb describe-table \ ```bash aws dynamodb scan \ - --table-name glad-entities --profile passbrains-ilisa-amplify \ + --table-name glad-entities --profile AWS_PROFILE \ --limit 10 ```