diff --git a/services/post-service/go.mod b/services/post-service/go.mod index 90d6f73..a5aba04 100644 --- a/services/post-service/go.mod +++ b/services/post-service/go.mod @@ -11,7 +11,6 @@ require ( github.com/cs6650/proto v0.0.0-00010101000000-000000000000 github.com/gin-gonic/gin v1.11.0 google.golang.org/grpc v1.76.0 - google.golang.org/protobuf v1.36.10 ) replace github.com/cs6650/proto => ../../proto @@ -61,4 +60,5 @@ require ( golang.org/x/text v0.27.0 // indirect golang.org/x/tools v0.34.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/services/post-service/internal/repository/post_repository.go b/services/post-service/internal/repository/post_repository.go index f68c51a..5d19b0b 100644 --- a/services/post-service/internal/repository/post_repository.go +++ b/services/post-service/internal/repository/post_repository.go @@ -27,17 +27,32 @@ func NewPostRepository(client *dynamodb.Client, tableName string) *PostRepositor // Create a new post and save to dynamodb func(r *PostRepository) CreatePost(ctx context.Context, post *pb.Post) error { - item, err := attributevalue.MarshalMap(post) - if err != nil { - return fmt.Errorf("failed to marshal post: %w", err) + // Manually create DynamoDB item with correct field names (post_id, user_id, etc.) + item := map[string]types.AttributeValue{ + "post_id": &types.AttributeValueMemberS{ + Value: fmt.Sprintf("%d", post.PostId), + }, + "user_id": &types.AttributeValueMemberN{ + Value: fmt.Sprintf("%d", post.UserId), + }, + "content": &types.AttributeValueMemberS{ + Value: post.Content, + }, + "timestamp": &types.AttributeValueMemberN{ + Value: fmt.Sprintf("%d", post.Timestamp), + }, } - - _, err = r.client.PutItem(ctx, &dynamodb.PutItemInput{ + + _, err := r.client.PutItem(ctx, &dynamodb.PutItemInput{ TableName: aws.String(r.tableName), - Item: item, + Item: item, }) - return err + if err != nil { + return fmt.Errorf("failed to create post: %w", err) + } + + return nil } // Retrieves a single post by PostID @@ -45,7 +60,7 @@ func(r *PostRepository)GetPost(ctx context.Context, postID int64)(*pb.Post, erro result, err := r.client.GetItem(ctx, &dynamodb.GetItemInput{ TableName: aws.String(r.tableName), Key: map[string]types.AttributeValue{ - "post_id": &types.AttributeValueMemberN{ + "post_id": &types.AttributeValueMemberS{ Value: fmt.Sprintf("%d", postID), }, }, diff --git a/services/post-service/terraform/main.tf b/services/post-service/terraform/main.tf index 9514b3d..065fd25 100644 --- a/services/post-service/terraform/main.tf +++ b/services/post-service/terraform/main.tf @@ -29,6 +29,9 @@ locals { # Configure DynamoDB tables module "dynamodb" { source = "./modules/dynamodb" + + table_name = var.dynamo_table + environment = var.environment } # Service-specific security group for ECS tasks @@ -134,6 +137,7 @@ module "ecs" { subnet_ids = var.public_subnet_ids security_group_ids = [aws_security_group.app.id] execution_role_arn = var.execution_role_arn + task_role_arn = var.task_role_arn # Task role for DynamoDB/SNS access log_group_name = module.logging.log_group_name target_group_arn = aws_lb_target_group.service.arn ecs_count = var.ecs_count @@ -152,7 +156,7 @@ module "ecs" { }, { name = "DYNAMO_TABLE" - value = var.dynamo_table + value = module.dynamodb.table_name }, { name = "POST_STRATEGY" diff --git a/services/post-service/terraform/modules/ecs/main.tf b/services/post-service/terraform/modules/ecs/main.tf index cf3d14e..f6bdaab 100644 --- a/services/post-service/terraform/modules/ecs/main.tf +++ b/services/post-service/terraform/modules/ecs/main.tf @@ -16,6 +16,7 @@ resource "aws_ecs_task_definition" "app" { cpu = var.cpu memory = var.memory execution_role_arn = var.execution_role_arn + task_role_arn = var.task_role_arn != "" ? var.task_role_arn : var.execution_role_arn # Use task_role if provided # Specify CPU architecture for Fargate runtime_platform { diff --git a/services/post-service/terraform/modules/ecs/variables.tf b/services/post-service/terraform/modules/ecs/variables.tf index dc66b31..7b91b67 100644 --- a/services/post-service/terraform/modules/ecs/variables.tf +++ b/services/post-service/terraform/modules/ecs/variables.tf @@ -28,6 +28,12 @@ variable "execution_role_arn" { description = "ECS Task Execution Role ARN (Innovation Sandbox with ISBStudent=true tag)" } +variable "task_role_arn" { + type = string + description = "ECS Task Role ARN for application permissions (DynamoDB, SNS)" + default = "" +} + variable "log_group_name" { type = string description = "CloudWatch log group name" diff --git a/services/post-service/terraform/variables.tf b/services/post-service/terraform/variables.tf index 5904d7d..27e114d 100644 --- a/services/post-service/terraform/variables.tf +++ b/services/post-service/terraform/variables.tf @@ -67,6 +67,12 @@ variable "execution_role_arn" { type = string } +variable "task_role_arn" { + description = "ARN of the ECS task role for application permissions" + type = string + default = "" +} + # ALB settings variable "alb_priority" { description = "Priority for ALB listener rule" diff --git a/terraform/main.tf b/terraform/main.tf index af3fbd5..b1e31e7 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -152,6 +152,7 @@ module "post_service" { # IAM role for ECS tasks execution_role_arn = module.iam.ecs_task_execution_role_arn + task_role_arn = module.iam.post_service_task_role_arn # Pass through necessary variables aws_region = var.aws_region diff --git a/terraform/modules/iam/main.tf b/terraform/modules/iam/main.tf index 9c2a365..976305b 100644 --- a/terraform/modules/iam/main.tf +++ b/terraform/modules/iam/main.tf @@ -28,6 +28,41 @@ resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" { policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } +# Post Service Task Role (for DynamoDB and SNS access) +resource "aws_iam_role" "post_service_task_role" { + name = "${var.project_name}-${var.environment}-post-task-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "${var.project_name}-${var.environment}-post-task-role" + Environment = var.environment + ISBStudent = "true" # Required for Innovation Sandbox IAM role creation + } +} + +# Attach AWS managed policies for DynamoDB and SNS +resource "aws_iam_role_policy_attachment" "post_dynamodb" { + role = aws_iam_role.post_service_task_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" +} + +resource "aws_iam_role_policy_attachment" "post_sns" { + role = aws_iam_role.post_service_task_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSNSFullAccess" +} + # Timeline Service Task Role (for DynamoDB and SQS access) resource "aws_iam_role" "timeline_service_task_role" { name = "${var.project_name}-${var.environment}-timeline-task-role" @@ -52,7 +87,6 @@ resource "aws_iam_role" "timeline_service_task_role" { } } -# Attach AWS managed policies for DynamoDB and SQS resource "aws_iam_role_policy_attachment" "timeline_dynamodb" { role = aws_iam_role.timeline_service_task_role.name policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" diff --git a/terraform/modules/iam/outputs.tf b/terraform/modules/iam/outputs.tf index f466fff..54faaed 100644 --- a/terraform/modules/iam/outputs.tf +++ b/terraform/modules/iam/outputs.tf @@ -3,6 +3,11 @@ output "ecs_task_execution_role_arn" { value = aws_iam_role.ecs_task_execution_role.arn } +output "post_service_task_role_arn" { + description = "ARN of the post service task role" + value = aws_iam_role.post_service_task_role.arn +} + output "timeline_service_task_role_arn" { description = "ARN of the timeline service task role" value = aws_iam_role.timeline_service_task_role.arn diff --git a/terraform/variables.tf b/terraform/variables.tf index 7004c9c..86f09ad 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -267,7 +267,7 @@ variable "post_service_ecs_count" { variable "post_service_post_strategy" { description = "Post strategy: push, pull, or hybrid" type = string - default = "hybrid" + default = "pull" }