Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 4 additions & 53 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package config

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"

"github.com/aws/aws-sdk-go-v2/aws"
Expand All @@ -14,22 +12,8 @@ import (
"github.com/sirupsen/logrus"
)

type PostgresSecret struct {
Username string `json:"username"`
Password string `json:"password"`
Database string `json:"database"`
Host string `json:"host"`
Port string `json:"port"`
DBClusterARN string `json:"dbClusterArn"`
SecretARN string `json:"secretArn"`
}

type Config struct {
DBClusterARN string
SecretARN string
DatabaseName string
PostgresConnStr string
AtProtoBaseURL string
AtProtoBaseURL string
}

type SecretsManagerAPI interface {
Expand All @@ -42,50 +26,17 @@ func LoadConfig(ctx context.Context, secretsClient SecretsManagerAPI) (*Config,
return nil, aws.Config{}, fmt.Errorf("failed to load AWS config: %w", err)
}

secretName := os.Getenv("POSTGRES_CONN_STR")
baseURL := os.Getenv("ATPROTO_BASE_URL")

if secretName == "" {
return nil, aws.Config{}, errors.New("POSTGRES_CONN_STR environment variable is required")
}

if baseURL == "" {
return nil, aws.Config{}, errors.New("ATPROTO_BASE_URL environment variable is required")
}

secretValue, err := RetrieveSecret(ctx, secretName, secretsClient)
if err != nil {
return nil, aws.Config{}, fmt.Errorf("failed to retrieve PostgreSQL secret: %w", err)
}

var secret PostgresSecret
if err := json.Unmarshal([]byte(secretValue), &secret); err != nil {
return nil, aws.Config{}, fmt.Errorf("failed to parse PostgreSQL secret JSON: %w", err) // **Fix: Proper error**
}

if secret.Database == "" || secret.Host == "" || secret.Username == "" || secret.Password == "" {
return nil, aws.Config{}, errors.New("parsed PostgreSQL secret is missing required fields")
}

formattedConnStr := fmt.Sprintf(
"postgres://%s:%s@%s:%s/%s?sslmode=require",
url.QueryEscape(secret.Username), url.QueryEscape(secret.Password),
secret.Host, secret.Port, secret.Database,
)

logrus.WithFields(logrus.Fields{
"host": secret.Host,
"database": secret.Database,
"dbClusterArn": secret.DBClusterARN,
"secretArn": secret.SecretARN,
}).Info("Successfully loaded PostgreSQL connection details")
"atproto_base_url": baseURL,
}).Info("Successfully loaded application configuration")

return &Config{
DBClusterARN: secret.DBClusterARN,
SecretARN: secret.SecretARN,
DatabaseName: secret.Database,
PostgresConnStr: formattedConnStr,
AtProtoBaseURL: baseURL,
AtProtoBaseURL: baseURL,
}, awsCfg, nil
}

Expand Down
51 changes: 4 additions & 47 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"context"
"encoding/json"
"errors"
"os"
"testing"
Expand Down Expand Up @@ -30,60 +29,22 @@ func TestLoadConfig(t *testing.T) {
mockSecretsClient := new(mockSecretsManagerClient)
ctx := context.Background()

validSecret := PostgresSecret{
Username: "user",
Password: "pass",
Database: "testdb",
Host: "localhost",
Port: "5432",
DBClusterARN: "arn:aws:rds:us-east-1:123456789012:cluster:test-cluster",
SecretARN: "arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret",
}

validSecretJSON, _ := json.Marshal(validSecret)

tests := []struct {
name string
envVars map[string]string
mockSecret *secretsmanager.GetSecretValueOutput
mockSecretErr error
expectedErrMsg string
}{
{
name: "Successful Config Load",
envVars: map[string]string{
"POSTGRES_CONN_STR": "test-secret",
"ATPROTO_BASE_URL": "https://example.com",
},
mockSecret: &secretsmanager.GetSecretValueOutput{
SecretString: aws.String(string(validSecretJSON)),
"ATPROTO_BASE_URL": "https://example.com",
},
mockSecretErr: nil,
expectedErrMsg: "",
},
{
name: "Corrupt JSON in Secret",
envVars: map[string]string{
"POSTGRES_CONN_STR": "test-secret",
"ATPROTO_BASE_URL": "https://example.com",
},
mockSecret: &secretsmanager.GetSecretValueOutput{
SecretString: aws.String(`{"invalid": "json"`),
},
mockSecretErr: nil,
expectedErrMsg: "failed to parse PostgreSQL secret JSON",
},
{
name: "Missing Required Fields in Secret JSON",
envVars: map[string]string{
"POSTGRES_CONN_STR": "test-secret",
"ATPROTO_BASE_URL": "https://example.com",
},
mockSecret: &secretsmanager.GetSecretValueOutput{
SecretString: aws.String(`{"username": "", "password": "", "database": "", "host": ""}`),
},
mockSecretErr: nil,
expectedErrMsg: "parsed PostgreSQL secret is missing required fields",
name: "Missing ATPROTO_BASE_URL",
envVars: map[string]string{},
expectedErrMsg: "ATPROTO_BASE_URL environment variable is required",
},
}

Expand All @@ -95,8 +56,6 @@ func TestLoadConfig(t *testing.T) {
}

mockSecretsClient.ExpectedCalls = nil
mockSecretsClient.On("GetSecretValue", mock.Anything, mock.Anything).
Return(test.mockSecret, test.mockSecretErr)

_, _, err := LoadConfig(ctx, mockSecretsClient)

Expand All @@ -106,8 +65,6 @@ func TestLoadConfig(t *testing.T) {
assert.Error(t, err)
assert.Contains(t, err.Error(), test.expectedErrMsg)
}

mockSecretsClient.AssertExpectations(t)
})
}
}
Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ require (
github.com/aws/aws-lambda-go v1.47.0
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.9
github.com/aws/aws-sdk-go-v2/service/rdsdata v1.28.1
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.1
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.41.1
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2
github.com/golang/mock v1.6.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.10.0
)
Expand All @@ -19,6 +20,7 @@ require (
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
Expand Down
35 changes: 31 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0io
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.41.1 h1:DEys4E5Q2p735j56lteNVyByIBDAlMrO5VIEd9RC0/4=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.41.1/go.mod h1:yYaWRnVSPyAmexW5t7G3TcuYoalYfT+xQwzWsvtUQ7M=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 h1:M1R1rud7HzDrfCdlBQ7NjnRsDNEhXO/vGhuD189Ggmk=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15/go.mod h1:uvFKBSq9yMPV4LGAi7N4awn4tLY+hKE35f8THes2mzQ=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/rdsdata v1.28.1 h1:E8NhIO2v519YEOWPNaFigCyrwgF0Z8E0nRWlYqhRTOc=
github.com/aws/aws-sdk-go-v2/service/rdsdata v1.28.1/go.mod h1:ah2CXasxl8doBpmLB5w4d3I1GDM8ykZpvdM9ac2Fq2Y=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.1 h1:+FDQfaijddP+aeT1BcT4ic8nZZc4hYUQVDL51CeCvb8=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.1/go.mod h1:yGhDiLKguA3iFJYxbrQkQiNzuy+ddxesSZYWVeeEH5Q=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2 h1:vlYXbindmagyVA3RS2SPd47eKZ00GZZQcr+etTviHtc=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2/go.mod h1:yGhDiLKguA3iFJYxbrQkQiNzuy+ddxesSZYWVeeEH5Q=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA=
Expand All @@ -33,6 +35,8 @@ github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxY
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
Expand All @@ -43,8 +47,31 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
102 changes: 102 additions & 0 deletions internal/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package db

import (
"context"
"encoding/json"
"fmt"
"time"

"github.com/ShareFrame/user-management/internal/models"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/sirupsen/logrus"
)

const (
QueryTimeout = 3 * time.Second
TableName = "Users"
)

type DynamoDBService interface {
CheckEmailExists(ctx context.Context, email string) (bool, error)
StoreUser(ctx context.Context, user models.CreateUserResponse, event models.UserRequest) error
}

type DynamoDBAPI interface {
PutItem(ctx context.Context, input *dynamodb.PutItemInput, opts ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error)
Query(ctx context.Context, input *dynamodb.QueryInput, opts ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error)
}

type DynamoDBClient struct {
Client DynamoDBAPI
}

func NewDynamoDBClient(client DynamoDBAPI) *DynamoDBClient {
return &DynamoDBClient{
Client: client,
}
}

func (d *DynamoDBClient) StoreUser(ctx context.Context, user models.CreateUserResponse, event models.UserRequest) error {
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
defer cancel()

themeJSON, err := json.Marshal(map[string]interface{}{})
if err != nil {
return fmt.Errorf("failed to marshal theme: %w", err)
}

item := map[string]types.AttributeValue{
"UserId": &types.AttributeValueMemberS{Value: user.DID},
"Email": &types.AttributeValueMemberS{Value: event.Email},
"Handle": &types.AttributeValueMemberS{Value: user.Handle},
"CreatedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
"ModifiedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
"Status": &types.AttributeValueMemberS{Value: "active"},
"Verified": &types.AttributeValueMemberBOOL{Value: false},
"Role": &types.AttributeValueMemberS{Value: "user"},
"DisplayName": &types.AttributeValueMemberS{Value: user.Handle},
"ProfilePicture": &types.AttributeValueMemberS{Value: ""},
"ProfileBanner": &types.AttributeValueMemberS{Value: ""},
"Theme": &types.AttributeValueMemberS{Value: string(themeJSON)},
"PrimaryColor": &types.AttributeValueMemberS{Value: "#FFFFFF"},
"SecondaryColor": &types.AttributeValueMemberS{Value: "#000000"},
}

_, err = d.Client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String(TableName),
Item: item,
})
if err != nil {
return fmt.Errorf("failed to store user in DynamoDB: %w", err)
}

return nil
}


func (d *DynamoDBClient) CheckEmailExists(ctx context.Context, email string) (bool, error) {
ctx, cancel := context.WithTimeout(ctx, QueryTimeout)
defer cancel()

result, err := d.Client.Query(ctx, &dynamodb.QueryInput{
TableName: aws.String("Users"),
IndexName: aws.String("Email-index"),
KeyConditionExpression: aws.String("Email = :email"),
ExpressionAttributeValues: map[string]types.AttributeValue{
":email": &types.AttributeValueMemberS{Value: email},
},
Limit: aws.Int32(1),
})

if err != nil {
logrus.WithError(err).Error("Error checking email existence")
return false, fmt.Errorf("failed to check email existence: %w", err)
}

return len(result.Items) > 0, nil
}



Loading