├── .gitignore ├── .vscode └── settings.json ├── BUILD.bazel ├── HelloWorld.yml ├── MODULE.bazel ├── MODULE.bazel.lock ├── README.md ├── WORKSPACE ├── api ├── authorizer │ ├── BUILD.bazel │ └── main.go ├── authorizer2 │ ├── BUILD.bazel │ └── main.go ├── authorizer3 │ ├── BUILD.bazel │ └── main.go ├── chat │ ├── AmazonRootCA1.pem │ ├── BUILD.bazel │ ├── main.go │ └── queries.tmpl ├── entity │ ├── BUILD.bazel │ ├── main.go │ ├── queries.json.tmpl │ ├── test_data │ │ ├── adlistitems.json │ │ └── post_shapeshift.json │ └── types.json.tmpl ├── gov │ ├── BUILD.bazel │ └── main.go ├── media │ ├── BUILD.bazel │ └── main.go ├── proxy │ ├── BUILD.bazel │ ├── main.go │ └── test_data │ │ └── cities.json ├── stream │ ├── BUILD.bazel │ └── main.go ├── user │ ├── BUILD.bazel │ └── main.go └── versait │ ├── BUILD.bazel │ └── main.go ├── app ├── image_feature │ ├── BUILD.bazel │ └── main.go └── knn_spherical │ ├── BUILD.bazel │ └── main.go ├── architecture ├── commands.md ├── plugins.md └── sites.md ├── cloudform ├── bucket.yml ├── main.yaml ├── search.sh ├── search.yaml └── users.yaml ├── edge └── renewable_redirect │ ├── BUILD.bazel │ └── index.js ├── email_templates ├── mytemplate.json └── temp_password.json ├── func ├── azure_hedge │ ├── BUILD.bazel │ └── main.go ├── convert_media │ ├── BUILD.bazel │ └── main.go ├── css_to_json │ ├── BUILD.bazel │ └── index.js ├── enforce_contract │ ├── BUILD.bazel │ └── index.js ├── grant_access │ ├── AmazonRootCA1.pem │ ├── BUILD.bazel │ └── main.go ├── hello_world │ ├── BUILD.bazel │ └── main.go ├── index_entity │ ├── BUILD.bazel │ └── main.go ├── readable_profiles │ ├── BUILD.bazel │ └── main.go ├── renewable_record │ ├── BUILD.bazel │ └── main.go ├── scss_to_css │ ├── BUILD.bazel │ └── index.js ├── validate_entity │ ├── BUILD.bazel │ └── main.go └── watttime_demo │ ├── BUILD.bazel │ └── main.go ├── go.mod ├── go.sum ├── iac ├── precheck-cognito │ ├── .gitignore │ ├── Pulumi.dev.yaml │ ├── Pulumi.yaml │ ├── eslintrc.json │ ├── index.ts │ ├── message │ │ ├── README.md │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ ├── post-confirmation │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ ├── project.json │ ├── pulumi.ts │ ├── save-user-data │ │ └── profile.ovpn │ ├── stack-reference.ts │ └── tsconfig.json └── precheck-oidc-auth │ ├── .eslintrc.json │ ├── .gitignore │ ├── Pulumi.dev.yaml │ ├── Pulumi.yaml │ ├── README.md │ ├── callback │ ├── index.js │ ├── package-lock.json │ └── package.json │ ├── index.ts │ ├── login │ ├── index.js │ ├── package-lock.json │ └── package.json │ ├── project.json │ ├── pulumi.ts │ ├── shared │ └── logger.js │ ├── stack-reference.ts │ ├── token │ ├── index.js │ ├── package-lock.json │ └── package.json │ ├── tsconfig.json │ └── user │ ├── index.js │ ├── package-lock.json │ └── package.json ├── job └── renewable_report │ ├── BUILD.bazel │ ├── main.go │ └── regions.json ├── lib ├── ads │ ├── BUILD.bazel │ └── ads.go ├── attr │ ├── BUILD.bazel │ └── attr.go ├── cc │ ├── BUILD.bazel │ └── cc.go ├── chat │ ├── BUILD.bazel │ └── chat.go ├── entity │ ├── BUILD.bazel │ └── entity.go ├── es │ ├── BUILD.bazel │ └── es.go ├── gov │ ├── BUILD.bazel │ └── gov.go ├── hedge │ ├── BUILD.bazel │ ├── index.js │ └── package.json ├── os │ ├── BUILD.bazel │ └── os.go ├── profiles │ ├── BUILD.bazel │ └── profiles.go ├── repo │ ├── BUILD.bazel │ └── repo.go ├── shapeshift │ ├── BUILD.bazel │ └── shapeshift.go ├── sign │ ├── BUILD.bazel │ └── sign.go ├── user │ ├── BUILD.bazel │ └── user.go ├── utils │ ├── BUILD.bazel │ ├── compact.go │ ├── jwt.go │ ├── log.go │ └── uuid.go ├── vocab │ ├── BUILD.bazel │ └── vocab.go └── watttime │ ├── BUILD.bazel │ └── watttime.go ├── manifests └── entity-api.yaml ├── mappings └── renewable-record.json ├── node_deps.bzl ├── package-lock.json ├── package.json ├── serverless-hedge.yml ├── serverless-knative.yml ├── serverless-versait.yml ├── serverless-watttime-demo.yml └── serverless.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .serverless 2 | bin 3 | node_modules 4 | 5 | bazel-bin 6 | bazel-goclassifieds 7 | bazel-out 8 | bazel-testlogs 9 | bazel-vertigo 10 | bazel-hedge 11 | 12 | private.dev.json 13 | private.prod.json 14 | 15 | api/entity/rtc-vertigo-dev.private-key.pem 16 | api/entity/rtc-vertigo-prod.private-key.pem 17 | 18 | .npmrc 19 | azure_credentials.json -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": null, 3 | "git.ignoreLimitWarning": true 4 | } -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:def.bzl", "gazelle") 2 | 3 | gazelle( 4 | name = "gazelle", 5 | prefix = "goclassifieds", 6 | ) 7 | -------------------------------------------------------------------------------- /HelloWorld.yml: -------------------------------------------------------------------------------- 1 | service: goclassifieds-hellworld 2 | provider: 3 | name: aws 4 | runtime: go1.x 5 | memorySize: 256 6 | timeout: 10 7 | httpApi: 8 | payload: '1.0' 9 | cors: true 10 | package: 11 | individually: false 12 | include: 13 | - bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/hello_world/hello_world_/hello_world 14 | exclude: 15 | - ./** 16 | - bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/hello_world/hello_world_/** 17 | resources: 18 | Resources: 19 | HelloWorldRole: 20 | Type: AWS::IAM::Role 21 | Properties: 22 | Path: "/" 23 | RoleName: "goclassifieds-helloworld-us-east-1-lambdaRole" 24 | AssumeRolePolicyDocument: 25 | Version: '2012-10-17' 26 | Statement: 27 | - Effect: Allow 28 | Principal: 29 | Service: 30 | - lambda.amazonaws.com 31 | Action: sts:AssumeRole 32 | ManagedPolicyArns: 33 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 34 | Policies: 35 | - PolicyName: goclassifieds-helloworld-lambda 36 | PolicyDocument: 37 | Version: '2012-10-17' 38 | Statement: 39 | - Effect: Allow 40 | Action: 41 | - logs:CreateLogGroup 42 | - logs:CreateLogStream 43 | - logs:PutLogEvents 44 | - logs:DescribeLogGroups 45 | - logs:DescribeLogStreams 46 | - logs:GetLogEvents 47 | - logs:FilterLogEvents 48 | Resource: 49 | - 'Fn::Join': 50 | - ':' 51 | - 52 | - 'arn:aws:logs' 53 | - Ref: 'AWS::Region' 54 | - Ref: 'AWS::AccountId' 55 | - 'log-group:/aws/lambda/*:*:*' 56 | - Effect: "Allow" 57 | Action: 58 | - "lambda:InvokeFunction" 59 | Resource: "*" 60 | - Effect: "Allow" 61 | Action: 62 | - "execute-api:Invoke" 63 | - "execute-api:ManageConnections" 64 | Resource: "*" 65 | functions: 66 | HelloWorld: 67 | handler: bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/hello_world/hello_world_/hello_world 68 | role: HelloWorldRole 69 | events: 70 | - httpApi: 71 | path: /helloworld 72 | method: GET -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "goclassifieds", 3 | version = "1.0.0", # Replace with your version 4 | ) 5 | 6 | bazel_dep(name = "rules_go", version = "0.46.0") 7 | bazel_dep(name = "bazel_gazelle", version = "0.35.0") 8 | bazel_dep(name = "rules_nodejs", version = "3.8.0") 9 | 10 | # Go toolchains and dependencies 11 | go_register_toolchains(version = "1.20.5") 12 | 13 | go_repository( 14 | name = "co_honnef_go_tools", 15 | importpath = "honnef.co/go/tools", 16 | version = "v0.0.1-2020.1.4", 17 | ) 18 | 19 | go_repository( 20 | name = "com_github_ajg_form", 21 | importpath = "github.com/ajg/form", 22 | version = "v1.5.1", 23 | ) 24 | 25 | go_repository( 26 | name = "com_github_andreasbriese_bbloom", 27 | importpath = "github.com/AndreasBriese/bbloom", 28 | version = "v0.0.0-20190306092124-e2d15f34fcf9", 29 | ) 30 | 31 | go_repository( 32 | name = "com_github_andybalholm_brotli", 33 | importpath = "github.com/andybalholm/brotli", 34 | version = "v1.0.1", 35 | ) 36 | 37 | go_repository( 38 | name = "com_github_armon_consul_api", 39 | importpath = "github.com/armon/consul-api", 40 | version = "v0.0.0-20180202201655-eb2c6b5be1b6", 41 | ) 42 | 43 | go_repository( 44 | name = "com_github_aws_smithy_go", 45 | importpath = "github.com/aws/smithy-go", 46 | version = "v1.8.0", 47 | ) 48 | 49 | go_repository( 50 | name = "com_github_aymerick_douceur", 51 | importpath = "github.com/aymerick/douceur", 52 | version = "v0.2.0", 53 | ) 54 | 55 | go_repository( 56 | name = "com_github_census_instrumentation_opencensus_proto", 57 | importpath = "github.com/census-instrumentation/opencensus-proto", 58 | version = "v0.2.1", 59 | ) 60 | 61 | go_repository( 62 | name = "com_github_chris_ramon_douceur", 63 | importpath = "github.com/chris-ramon/douceur", 64 | version = "v0.2.0", 65 | ) 66 | 67 | go_repository( 68 | name = "com_github_client9_misspell", 69 | importpath = "github.com/client9/misspell", 70 | version = "v0.3.4", 71 | ) 72 | 73 | # Add all other `go_repository` rules here from your WORKSPACE file. 74 | 75 | # Node.js rules and dependencies 76 | node_repositories( 77 | node_version = "16.6.2", 78 | package_json = ["//:package.json"], 79 | ) 80 | 81 | npm_install( 82 | name = "npm", 83 | package_json = "//:package.json", 84 | package_lock_json = "//:package-lock.json", 85 | ) 86 | 87 | # Rules for esbuild (platform-specific binaries) 88 | http_archive( 89 | name = "esbuild_darwin", 90 | build_file_content = """exports_files(["bin/esbuild"])""", 91 | urls = [ 92 | "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.12.1.tgz", 93 | ], 94 | sha256 = "efb34692bfa34db61139eb8e46cd6cf767a42048f41c8108267279aaf58a948f", 95 | strip_prefix = "package", 96 | ) 97 | 98 | http_archive( 99 | name = "esbuild_windows", 100 | build_file_content = """exports_files(["esbuild.exe"])""", 101 | urls = [ 102 | "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.12.1.tgz", 103 | ], 104 | sha256 = "10439647b11c7fd1d9647fd98d022fe2188b4877d2d0b4acbe857f4e764b17a9", 105 | strip_prefix = "package", 106 | ) 107 | 108 | http_archive( 109 | name = "esbuild_linux", 110 | build_file_content = """exports_files(["bin/esbuild"])""", 111 | urls = [ 112 | "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.12.1.tgz", 113 | ], 114 | sha256 = "de8409b90ec3c018ffd899b49ed5fc462c61b8c702ea0f9da013e0e1cd71549a", 115 | strip_prefix = "package", 116 | ) 117 | 118 | # Additional dependencies 119 | bazel_dep(name = "rules_go", version = "0.46.0") 120 | bazel_dep(name = "bazel_gazelle", version = "0.35.0") 121 | 122 | # load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") 123 | 124 | gazelle_dependencies() 125 | 126 | node_repositories( 127 | node_version = "16.6.2", 128 | package_json = ["//:package.json"], 129 | ) 130 | 131 | npm_install( 132 | name = "npm", 133 | package_json = "//:package.json", 134 | package_lock_json = "//:package-lock.json", 135 | ) 136 | 137 | # Register toolchains and dependencies 138 | go_register_toolchains(version = "1.20.5") 139 | 140 | # Add all the `go_repository` rules from your WORKSPACE file here 141 | # For example: 142 | 143 | go_repository( 144 | name = "co_honnef_go_tools", 145 | importpath = "honnef.co/go/tools", 146 | version = "v0.0.1-2020.1.4", 147 | ) 148 | 149 | go_repository( 150 | name = "com_github_ajg_form", 151 | importpath = "github.com/ajg/form", 152 | version = "v1.5.1", 153 | ) 154 | 155 | # Continue adding all `go_repository` rules here... 156 | 157 | # Other configurations and dependencies need to be migrated similarly. -------------------------------------------------------------------------------- /api/authorizer/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "authorizer", 5 | embed = [":authorizer_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/authorizer", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "authorizer_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/authorizer", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 27 | "@com_github_micahparks_keyfunc//:go_default_library" 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /api/authorizer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "goclassifieds/lib/utils" 8 | 9 | "github.com/MicahParks/keyfunc" 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | "github.com/golang-jwt/jwt/v4" 13 | ) 14 | 15 | var handler Handler 16 | 17 | type Handler func(req *events.APIGatewayWebsocketProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) 18 | 19 | type ActionContext struct { 20 | UserPoolId string 21 | } 22 | 23 | // Authorizer custom api authorizer 24 | func Authorizer(request *events.APIGatewayWebsocketProxyRequest, ac *ActionContext) (events.APIGatewayCustomAuthorizerResponse, error) { 25 | token := request.QueryStringParameters["token"] 26 | 27 | usageLog := &utils.LogUsageLambdaInput{ 28 | // UserId: GetUserId(req), 29 | //Username: GetUsername(req), 30 | UserId: "null", 31 | Username: "null", 32 | Resource: request.RequestContext.RouteKey, 33 | Path: request.RequestContext.EventType, 34 | RequestId: request.RequestContext.RequestID, 35 | Intensities: "null", 36 | Regions: "null", 37 | Region: "null", 38 | Service: "null", 39 | Repository: "null", 40 | Organization: "null", 41 | } 42 | _, hedged := request.Headers["x-hedge-region"] 43 | if hedged { 44 | usageLog.Intensities = request.Headers["x-hedge-intensities"] 45 | usageLog.Regions = request.Headers["x-hedge-regions"] 46 | usageLog.Region = request.Headers["x-hedge-region"] 47 | usageLog.Service = request.Headers["x-hedge-service"] 48 | } 49 | 50 | utils.LogUsageForLambdaWithInput(usageLog) 51 | 52 | // ctx := context.Background() 53 | // Fetch all keys 54 | // jwkSet, err := jwk.Fetch(ctx, "https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json") 55 | jwks, err := keyfunc.Get("https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json", keyfunc.Options{}) 56 | if err != nil { 57 | log.Fatalln("Unable to fetch keys") 58 | } 59 | 60 | log.Print("fectehd keys") 61 | 62 | // Verify 63 | t, err := jwt.Parse(token, jwks.Keyfunc) 64 | if err != nil || !t.Valid { 65 | log.Print(err) 66 | log.Fatalln("Unauthorized") 67 | } 68 | 69 | log.Print("authorized") 70 | 71 | claims := t.Claims.(jwt.MapClaims) 72 | 73 | log.Print("got claims") 74 | 75 | claims["cognito:groups"] = nil //claims["cognito:groups"] 76 | claims["cognito:roles"] = nil //claims["cognito:groups"] 77 | 78 | return events.APIGatewayCustomAuthorizerResponse{ 79 | PrincipalID: "me", 80 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 81 | Version: "2012-10-17", 82 | Statement: []events.IAMPolicyStatement{ 83 | { 84 | Action: []string{"execute-api:Invoke"}, 85 | Effect: "Allow", 86 | Resource: []string{"*"}, 87 | // Resource: []string{request.Resource}, 88 | }, 89 | }, 90 | }, 91 | Context: claims, 92 | }, nil 93 | } 94 | 95 | func InitializeHandler(ac ActionContext) Handler { 96 | return func(req *events.APIGatewayWebsocketProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) { 97 | return Authorizer(req, &ac) 98 | } 99 | } 100 | 101 | func init() { 102 | log.Printf("Gin cold start") 103 | actionContext := ActionContext{ 104 | UserPoolId: os.Getenv("USER_POOL_ID"), 105 | } 106 | handler = InitializeHandler(actionContext) 107 | } 108 | 109 | func main() { 110 | log.SetFlags(0) 111 | lambda.Start(handler) 112 | } 113 | -------------------------------------------------------------------------------- /api/authorizer2/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "authorizer2", 5 | embed = [":authorizer2_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/authorizer2", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "authorizer2_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/authorizer2", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 27 | "@com_github_micahparks_keyfunc//:go_default_library" 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /api/authorizer2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "goclassifieds/lib/utils" 5 | "log" 6 | "os" 7 | 8 | "github.com/MicahParks/keyfunc" 9 | "github.com/aws/aws-lambda-go/events" 10 | "github.com/aws/aws-lambda-go/lambda" 11 | "github.com/golang-jwt/jwt/v4" 12 | ) 13 | 14 | var handler Handler 15 | 16 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) 17 | 18 | type ActionContext struct { 19 | UserPoolId string 20 | } 21 | 22 | // Authorizer custom api authorizer 23 | func Authorizer(request *events.APIGatewayProxyRequest, ac *ActionContext) (events.APIGatewayCustomAuthorizerResponse, error) { 24 | token1 := request.Headers["authorization"] 25 | 26 | utils.LogUsageForHttpRequest(request) 27 | 28 | log.Printf("%v", request) 29 | log.Printf("token is %s", token1) 30 | 31 | if token1 == "" { 32 | log.Print("unauthorized request pass thru") 33 | return events.APIGatewayCustomAuthorizerResponse{ 34 | PrincipalID: "me", 35 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 36 | Version: "2012-10-17", 37 | Statement: []events.IAMPolicyStatement{ 38 | { 39 | Action: []string{"execute-api:*"}, 40 | Effect: "Allow", 41 | Resource: []string{"*"}, 42 | // Resource: []string{request.Re}, 43 | }, 44 | }, 45 | }, 46 | }, nil 47 | } 48 | 49 | token := token1[7:] 50 | log.Printf("token after is %s", token) 51 | 52 | jwks, err := keyfunc.Get("https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json", keyfunc.Options{}) 53 | if err != nil { 54 | log.Fatalln("Unable to fetch keys") 55 | } 56 | 57 | log.Print("fectehd keys") 58 | 59 | // Verify 60 | t, err := jwt.Parse(token, jwks.Keyfunc) 61 | if err != nil || !t.Valid { 62 | log.Print(err) 63 | log.Fatalln("Unauthorized") 64 | } 65 | 66 | log.Print("authorized") 67 | 68 | claims := t.Claims.(jwt.MapClaims) 69 | 70 | log.Print("got claims") 71 | 72 | claims["cognito:groups"] = nil //claims["cognito:groups"] 73 | claims["cognito:roles"] = nil //claims["cognito:groups"] 74 | 75 | return events.APIGatewayCustomAuthorizerResponse{ 76 | PrincipalID: "me", 77 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 78 | Version: "2012-10-17", 79 | Statement: []events.IAMPolicyStatement{ 80 | { 81 | Action: []string{"execute-api:*"}, 82 | Effect: "Allow", 83 | Resource: []string{"*"}, 84 | // Resource: []string{request.Re}, 85 | }, 86 | }, 87 | }, 88 | Context: claims, 89 | }, nil 90 | } 91 | 92 | func InitializeHandler(ac ActionContext) Handler { 93 | return func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) { 94 | return Authorizer(req, &ac) 95 | } 96 | } 97 | 98 | func init() { 99 | log.Printf("Gin cold start") 100 | actionContext := ActionContext{ 101 | UserPoolId: os.Getenv("USER_POOL_ID"), 102 | } 103 | handler = InitializeHandler(actionContext) 104 | } 105 | 106 | func main() { 107 | log.SetFlags(0) 108 | lambda.Start(handler) 109 | } 110 | -------------------------------------------------------------------------------- /api/authorizer3/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "authorizer3", 5 | embed = [":authorizer3_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/authorizer3", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "authorizer3_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/authorizer3", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 27 | "@com_github_micahparks_keyfunc//:go_default_library" 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /api/authorizer3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "goclassifieds/lib/utils" 5 | "log" 6 | "os" 7 | "errors" 8 | 9 | "github.com/MicahParks/keyfunc" 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | "github.com/golang-jwt/jwt/v4" 13 | ) 14 | 15 | var handler Handler 16 | 17 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) 18 | 19 | type ActionContext struct { 20 | UserPoolId string 21 | } 22 | 23 | // Authorizer custom api authorizer 24 | func Authorizer(request *events.APIGatewayProxyRequest, ac *ActionContext) (events.APIGatewayCustomAuthorizerResponse, error) { 25 | token1 := request.Headers["authorization"] 26 | 27 | utils.LogUsageForHttpRequest(request) 28 | 29 | log.Printf("%v", request) 30 | log.Printf("token is %s", token1) 31 | 32 | if token1 == "" { 33 | // For now lock it down but eventually needs to support anonymous users. 34 | /*log.Print("unauthorized request pass thru") 35 | return events.APIGatewayCustomAuthorizerResponse{ 36 | PrincipalID: "me", 37 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 38 | Version: "2012-10-17", 39 | Statement: []events.IAMPolicyStatement{ 40 | { 41 | Action: []string{"execute-api:*"}, 42 | Effect: "Allow", 43 | Resource: []string{"*"}, 44 | // Resource: []string{request.Re}, 45 | }, 46 | }, 47 | }, 48 | }, nil*/ 49 | log.Print("Unauthorized requires token") 50 | return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized requires token") 51 | } 52 | 53 | token := token1[7:] 54 | log.Printf("token after is %s", token) 55 | 56 | // jwks, err := keyfunc.Get("https://cognito-idp.us-east-1.amazonaws.com/"+ac.UserPoolId+"/.well-known/jwks.json", keyfunc.Options{}) 57 | 58 | // --------------------- Canva integration for jwks ---------------------- 59 | 60 | appId := "AAGJBJc7pZs" 61 | jwks, err := keyfunc.Get("https://api.canva.com/rest/v1/apps/" + appId + "/jwks", keyfunc.Options{}) 62 | 63 | // --------------------- End Canva integration for jwks ---------------------- 64 | 65 | if err != nil { 66 | log.Print("Unable to fetch keys") 67 | } 68 | 69 | log.Print("fectehd keys") 70 | 71 | // Verify 72 | t, err := jwt.Parse(token, jwks.Keyfunc) 73 | if err != nil || !t.Valid { 74 | log.Print(err) 75 | log.Print("Unauthorized") 76 | return events.APIGatewayCustomAuthorizerResponse{}, err 77 | } 78 | 79 | log.Print("authorized") 80 | 81 | claims := t.Claims.(jwt.MapClaims) 82 | 83 | log.Print("got claims") 84 | 85 | // -------- Canva Integration for Claims Compatibility --------------------------------- 86 | // remap to source of truth when shared between providers like: user id/sub, user name, etc. 87 | // Otherwise overload with claims that can be pulled out once request identified as a specific auth host ie.cognito, canva, etc. 88 | 89 | // log expected claims for canva 90 | log.Printf("Canva userId: %s", claims["userId"]) 91 | log.Printf("Canva designId: %s", claims["brandId"]) 92 | log.Printf("Canva teamId: %s", claims["aud"]) 93 | 94 | // Cognito is the source of truth so map Canva userId to sub which means the same thing. 95 | claims["sub"] = claims["userId"] 96 | 97 | // Canva does not have usernames just optional display names. So for now just reuse the userId. 98 | // We could use the connect api to grab the display name if it exists... but why really... 99 | claims["cognito:username"] = claims["userId"] 100 | 101 | // --------- End Canva Integration for Claims Compatibility ------------------- 102 | 103 | claims["cognito:groups"] = nil //claims["cognito:groups"] 104 | claims["cognito:roles"] = nil //claims["cognito:groups"] 105 | 106 | return events.APIGatewayCustomAuthorizerResponse{ 107 | PrincipalID: "me", 108 | PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{ 109 | Version: "2012-10-17", 110 | Statement: []events.IAMPolicyStatement{ 111 | { 112 | Action: []string{"execute-api:*"}, 113 | Effect: "Allow", 114 | Resource: []string{"*"}, 115 | // Resource: []string{request.Re}, 116 | }, 117 | }, 118 | }, 119 | Context: claims, 120 | }, nil 121 | } 122 | 123 | func InitializeHandler(ac ActionContext) Handler { 124 | return func(req *events.APIGatewayProxyRequest) (events.APIGatewayCustomAuthorizerResponse, error) { 125 | return Authorizer(req, &ac) 126 | } 127 | } 128 | 129 | func init() { 130 | log.Printf("Gin cold start") 131 | actionContext := ActionContext{ 132 | UserPoolId: os.Getenv("USER_POOL_ID"), 133 | } 134 | handler = InitializeHandler(actionContext) 135 | } 136 | 137 | func main() { 138 | log.SetFlags(0) 139 | lambda.Start(handler) 140 | } 141 | -------------------------------------------------------------------------------- /api/chat/AmazonRootCA1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /api/chat/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "chat", 5 | embed = [":chat_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/chat", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "chat_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/chat", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/entity", 24 | "//lib/utils", 25 | "@com_github_aws_aws_lambda_go//events", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_aws_aws_sdk_go//aws", 28 | "@com_github_aws_aws_sdk_go//aws/session", 29 | "@com_github_aws_aws_sdk_go//service/apigatewaymanagementapi", 30 | "@com_github_aws_aws_sdk_go//service/lambda", 31 | "@com_github_gocql_gocql//:gocql", 32 | "@com_github_tangzero_inflector//:inflector", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /api/chat/queries.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "chatconversations" }} 2 | SELECT 3 | recipientid AS recipientId, 4 | recipientlabel AS recipientLabel, 5 | userid AS userId 6 | FROM 7 | chatconversations 8 | WHERE 9 | userid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 10 | {{end}} 11 | 12 | {{ define "chatmessages" }} 13 | SELECT 14 | senderid, 15 | recipientid, 16 | createdat, 17 | message 18 | FROM 19 | chatmessages 20 | WHERE 21 | senderid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 22 | AND 23 | recipientid = {{ bindValue (index .Req.QueryStringParameters "recipientId") }} 24 | {{end}} 25 | 26 | {{ define "_chatmessages_inverse" }} 27 | SELECT 28 | senderid, 29 | recipientid, 30 | createdat, 31 | message 32 | FROM 33 | chatmessages 34 | WHERE 35 | recipientid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 36 | AND 37 | senderid = {{ bindValue (index .Req.QueryStringParameters "recipientId") }} 38 | {{end}} 39 | 40 | {{ define "_chatconnections" }} 41 | SELECT 42 | connid, 43 | userid, 44 | createdat 45 | FROM 46 | chatconnections 47 | WHERE 48 | userid = {{ bindValue .Req.RequestContext.Authorizer.claims.sub }} 49 | {{ end }} 50 | 51 | {{ define "_chatconnections_inverse" }} 52 | SELECT 53 | connid, 54 | userid, 55 | createdat 56 | FROM 57 | chatconnections 58 | WHERE 59 | userid = {{ bindValue (index .Metadata "recipientId" ) }} 60 | {{ end }} 61 | 62 | {{define "leads" }} 63 | SELECT 64 | adid, 65 | profileid, 66 | senderid, 67 | phone, 68 | email, 69 | createdat 70 | FROM 71 | leads 72 | WHERE 73 | profileid = {{ bindValue (index .Req.QueryStringParameters "profileId") }} 74 | {{ end }} 75 | 76 | {{ define "pages" }} 77 | SELECT 78 | site, 79 | path, 80 | title, 81 | body, 82 | published, 83 | createdAt 84 | FROM 85 | pages 86 | WHERE 87 | site = {{ bindValue (index .Req.QueryStringParameters "site") }} 88 | {{ end }} 89 | 90 | {{ define "gridlayouts"}} 91 | SELECT 92 | id, 93 | site, 94 | weight, 95 | cols, 96 | rows, 97 | x, 98 | y 99 | FROM 100 | gridlayouts 101 | WHERE 102 | site = {{ bindValue (index .Req.QueryStringParameters "site") }} 103 | {{ end }} -------------------------------------------------------------------------------- /api/entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | #load("@rules_oci//oci:oci.bzl", "oci_image") 3 | 4 | # Build the Go library 5 | go_library( 6 | name = "entity_lib", 7 | srcs = ["main.go"], 8 | importpath = "goclassifieds/api/entity", 9 | visibility = ["//visibility:private"], 10 | deps = [ 11 | "//lib/shapeshift", 12 | "@com_github_aws_aws_lambda_go//events", 13 | "@com_github_aws_aws_lambda_go//lambda", 14 | ], 15 | ) 16 | 17 | # Build the Go binary 18 | go_binary( 19 | name = "entity", 20 | embed = [":entity_lib"], 21 | goarch = "amd64", 22 | goos = "linux", 23 | importpath = "goclassifieds/api/entity", 24 | visibility = ["//visibility:public"], 25 | out = "bootstrap", # Output binary name 26 | ) -------------------------------------------------------------------------------- /api/entity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "goclassifieds/lib/shapeshift" 7 | 8 | "github.com/aws/aws-lambda-go/events" 9 | "github.com/aws/aws-lambda-go/lambda" 10 | ) 11 | 12 | var handler shapeshift.Handler 13 | 14 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) 15 | 16 | func init() { 17 | ac := shapeshift.ShapeshiftActionContext() 18 | handler = shapeshift.InitializeHandler(ac) 19 | } 20 | 21 | func main() { 22 | log.SetFlags(0) 23 | lambda.Start(handler) 24 | } 25 | -------------------------------------------------------------------------------- /api/entity/test_data/adlistitems.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "/entity/ad/adlistitems", 3 | "path": "/entity/ad/adlistitems", 4 | "httpMethod": "GET", 5 | "headers": { 6 | "Content-Length": "625", 7 | "Content-Type": "application/json", 8 | "Host": "p1vgub4jtb.execute-api.us-east-1.amazonaws.com", 9 | "Postman-Token": "71cbea13-de45-4160-b91c-2695baf650e2", 10 | "User-Agent": "PostmanRuntime/6.4.1", 11 | "X-Amzn-Trace-Id": "Root=1-5ec7a5d9-e77a95ff2fc7420140ab5832", 12 | "X-Forwarded-For": "75.118.234.96", 13 | "X-Forwarded-Port": "443", 14 | "X-Forwarded-Proto": "https", 15 | "accept": "*/*", 16 | "accept-encoding": "gzip, deflate", 17 | "authorization": "Bearer eyJraWQiOiJVTlRrNGhWaFVycTBrOHVBR3ZEWXNcL3cwZ1wvaVcrYjlQd2VPYk1iTTZTXC93PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlMzZiNDJmZS1iMDljLTQ1MTQtYTUxOS1lMTc4YmI1Mjk1N2UiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIGFkc19hcGlcL2Fkc19hcGkgbWVkaWFfYXBpXC9tZWRpYV9hcGkgb3BlbmlkIHByb2ZpbGUgdGF4b25vbXlfYXBpXC90YXhvbm9teV9hcGkgcHJvZmlsZXNfYXBpXC9wcm9maWxlc19hcGkgY2hhdFwvY2hhdCIsImF1dGhfdGltZSI6MTU5MDUwNzk1NiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfejhQaEszRDhWIiwiZXhwIjoxNTkwNTExNTU2LCJpYXQiOjE1OTA1MDc5NTYsInZlcnNpb24iOjIsImp0aSI6ImFlZTY0Yzg1LTUxYWUtNDliNC1hZGY5LTZlMTBkMTc1ZTkxYiIsImNsaWVudF9pZCI6IjNsa2lkbjc5cTRqcThuYWhrZWNzZ3VsZWlqIiwidXNlcm5hbWUiOiJ0b2RkeiJ9.O3kdN7_FZiPwBo7OGecOsBRdrE13qASe35f2pg12qLbCyVwqO4aiPmQX82NxgAoEHxs6Arq3sTkORfeCTf5pUzgEIM-9KhDaVSbUdje4Wge-KezheRVY9i1ssxvq02_8D_5gY7Ak_K7PnEV_cblF3szGeQ6xtYw-5WIbxExn6aBK0OBSCvzqEc0lY6laP3Kjjq7eqAkkFsigiFmbDrrAStYCZbtId-tYiDv2wdGDu9QPX7zhlv8xbyTHlxRDJNR39e8WQcwVn38YCHbJoFhAqRP3NUKTzFYPAA4Ta4prY1tfmtKT3jWGqCcvk4_fmMnLQXsbvU1p-mPqdmcldViNnw", 18 | "cache-control": "no-cache" 19 | }, 20 | "multiValueHeaders": { 21 | "Content-Length": [ 22 | "625" 23 | ], 24 | "Content-Type": [ 25 | "application/json" 26 | ], 27 | "Host": [ 28 | "p1vgub4jtb.execute-api.us-east-1.amazonaws.com" 29 | ], 30 | "Postman-Token": [ 31 | "71cbea13-de45-4160-b91c-2695baf650e2" 32 | ], 33 | "User-Agent": [ 34 | "PostmanRuntime/6.4.1" 35 | ], 36 | "X-Amzn-Trace-Id": [ 37 | "Root=1-5ec7a5d9-e77a95ff2fc7420140ab5832" 38 | ], 39 | "X-Forwarded-For": [ 40 | "75.118.234.96" 41 | ], 42 | "X-Forwarded-Port": [ 43 | "443" 44 | ], 45 | "X-Forwarded-Proto": [ 46 | "https" 47 | ], 48 | "accept": [ 49 | "*/*" 50 | ], 51 | "accept-encoding": [ 52 | "gzip, deflate" 53 | ], 54 | "authorization": [ 55 | "Bearer eyJraWQiOiJVTlRrNGhWaFVycTBrOHVBR3ZEWXNcL3cwZ1wvaVcrYjlQd2VPYk1iTTZTXC93PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlMzZiNDJmZS1iMDljLTQ1MTQtYTUxOS1lMTc4YmI1Mjk1N2UiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIGFkc19hcGlcL2Fkc19hcGkgbWVkaWFfYXBpXC9tZWRpYV9hcGkgb3BlbmlkIHByb2ZpbGUgdGF4b25vbXlfYXBpXC90YXhvbm9teV9hcGkgcHJvZmlsZXNfYXBpXC9wcm9maWxlc19hcGkgY2hhdFwvY2hhdCIsImF1dGhfdGltZSI6MTU5MDE0MTQ1MCwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfejhQaEszRDhWIiwiZXhwIjoxNTkwMTQ1MDUwLCJpYXQiOjE1OTAxNDE0NTAsInZlcnNpb24iOjIsImp0aSI6IjQ4OWVkMTNhLTM5ZDUtNDcxMS1iNTI1LTllNTlhODdjYTM2MiIsImNsaWVudF9pZCI6IjNsa2lkbjc5cTRqcThuYWhrZWNzZ3VsZWlqIiwidXNlcm5hbWUiOiJ0b2RkeiJ9.IhzKi4MilXOArmX75VmIhRlVSqYQl_0wLibWETAukwcAnp_Zl2I6_p8ryA9wEudjqXFBpRcZa2wlZh2ZZ4Jd8nuFq9PcwD5WBTTeQ0dSlHQ6ls9FFSigqUW-w62JM1RwI52ImU8G_ZvVJlX9Bc6UbRlT6BzLVcJMco4k3oSWoU9mYbn-u2KBQ9x2SJEH6YHBmOJxgty5jo_Q1xPHTRjnY9lNI3oVOUCIbIjHEcw9Kj5MMeWC4zHmxhaae6gO1GMI9RMydpnqL1AJ2jpWXgJDyudDTGIACtryBZKlVdGhq-dbfURTvarKGNodrJGE9do3zZF7NbraSgS7DnJKv5bv7g" 56 | ], 57 | "cache-control": [ 58 | "no-cache" 59 | ] 60 | }, 61 | "queryStringParameters": { 62 | "typeId": "5665f5f8-5533-4aa1-96f4-b78e02714de0" 63 | }, 64 | "multiValueQueryStringParameters": null, 65 | "pathParameters": { 66 | "entityName": "ad", 67 | "queryName": "adlistitems" 68 | }, 69 | "stageVariables": null, 70 | "requestContext": { 71 | "accountId": "989992233821", 72 | "resourceId": "GET /entity/ad/adlistitems", 73 | "stage": "$default", 74 | "requestId": "M7bZ9hnfoAMEVcw=", 75 | "identity": { 76 | "cognitoIdentityPoolId": "", 77 | "accountId": "", 78 | "cognitoIdentityId": "", 79 | "caller": "", 80 | "apiKey": "", 81 | "apiKeyId": "", 82 | "accessKey": "", 83 | "sourceIp": "75.118.234.96", 84 | "cognitoAuthenticationType": "", 85 | "cognitoAuthenticationProvider": "", 86 | "userArn": "", 87 | "userAgent": "PostmanRuntime/6.4.1", 88 | "user": "" 89 | }, 90 | "resourcePath": "/entity/ad/adlistitems", 91 | "authorizer": { 92 | "claims": { 93 | "auth_time": "1590141450", 94 | "client_id": "3lkidn79q4jq8nahkecsguleij", 95 | "exp": "1590145050", 96 | "iat": "1590141450", 97 | "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_z8PhK3D8V", 98 | "jti": "489ed13a-39d5-4711-b525-9e59a87ca362", 99 | "sub": "e36b42fe-b09c-4514-a519-e178bb52957e", 100 | "token_use": "access", 101 | "username": "toddz", 102 | "version": "2" 103 | }, 104 | "scopes": [ 105 | "aws.cognito.signin.user.admin", 106 | "ads_api/ads_api", 107 | "media_api/media_api", 108 | "openid", 109 | "profile", 110 | "taxonomy_api/taxonomy_api", 111 | "profiles_api/profiles_api", 112 | "chat/chat" 113 | ] 114 | }, 115 | "httpMethod": "GET", 116 | "apiId": "p1vgub4jtb" 117 | }, 118 | "body": "" 119 | } -------------------------------------------------------------------------------- /api/entity/test_data/post_shapeshift.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "/{owner}/{repo}/shapeshifter", 3 | "path": "/my-owner/my-repo/shapeshifter", 4 | "httpMethod": "POST", 5 | "headers": { 6 | "Content-Type": "application/json", 7 | "Authorization": "Bearer eyJraWQiOiJVTlRrNGhWaFVycTBrOHVBR3ZEWXNcL3cwZ1wvaVcrYjlQd2VPYk1iTTZTXC93PSIsImFsZyI6IlJTMjU2In0..." 8 | }, 9 | "pathParameters": { 10 | "owner": "my-owner", 11 | "repo": "my-repo" 12 | }, 13 | "queryStringParameters": {}, 14 | "requestContext": { 15 | "authorizer": { 16 | "claims": { 17 | "sub": "mock-user-id", 18 | "username": "mock-username" 19 | } 20 | } 21 | }, 22 | "body": "{\"name\": \"MyEntity\", \"type\": \"example\", \"data\": {\"key\": \"value\"}}" 23 | } -------------------------------------------------------------------------------- /api/entity/types.json.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "all" }} 2 | [ 3 | { 4 | "id": "27b8e62c-fcec-450c-9774-579a9549917f", 5 | "owner": "system", 6 | "overlay": false, 7 | "name": "general", 8 | "target": "ad", 9 | "attributes": [], 10 | "filters": [] 11 | }, 12 | { 13 | "id": "5665f5f8-5533-4aa1-96f4-b78e02714de0", 14 | "owner": "system", 15 | "overlay": false, 16 | "name": "realestate", 17 | "target": "ad", 18 | "attributes": [ 19 | { 20 | "name": "price", 21 | "type": 0, 22 | "label": "Asking Price", 23 | "required": true, 24 | "widget": "text", 25 | "attributes": [] 26 | }, 27 | { 28 | "name": "beds", 29 | "type": 0, 30 | "label": "Beds", 31 | "required": true, 32 | "widget": "text", 33 | "attributes": [] 34 | }, 35 | { 36 | "name": "baths", 37 | "type": 0, 38 | "label": "Baths", 39 | "required": true, 40 | "widget": "text", 41 | "attributes": [] 42 | }, 43 | { 44 | "name": "sqft", 45 | "type": 0, 46 | "label": "Sqft", 47 | "required": true, 48 | "widget": "text", 49 | "attributes": [] 50 | } 51 | ], 52 | "filters": [ 53 | { 54 | "name": "beds", 55 | "type": 0, 56 | "label": "Beds", 57 | "required": false, 58 | "widget": "text", 59 | "attributes": [] 60 | }, 61 | { 62 | "name": "baths", 63 | "type": 0, 64 | "label": "Baths", 65 | "required": false, 66 | "widget": "text", 67 | "attributes": [] 68 | } 69 | ] 70 | }, 71 | { 72 | "id": "d9bf054c-2a43-4976-a35e-25bd4c58417e", 73 | "owner": "system", 74 | "overlay": false, 75 | "name": "rentals", 76 | "target": "ad", 77 | "attributes": [], 78 | "filters": [] 79 | }, 80 | { 81 | "id": "ef5c1cdb-95b3-45be-ba01-b6745d9744f9", 82 | "owner": "system", 83 | "overlay": false, 84 | "name": "autos", 85 | "target": "ad", 86 | "attributes": [ 87 | { 88 | "name": "ymm", 89 | "type": 2, 90 | "label": "YMM", 91 | "required": false, 92 | "widget": "ymm_selector", 93 | "attributes": [ 94 | { 95 | "name": "year", 96 | "type": 0, 97 | "label": "Year", 98 | "required": false, 99 | "widget": "text", 100 | "attributes": [] 101 | }, 102 | { 103 | "name": "make", 104 | "type": 1, 105 | "label": "Make", 106 | "required": false, 107 | "widget": "text", 108 | "attributes": [] 109 | }, 110 | { 111 | "name": "model", 112 | "type": 1, 113 | "label": "Model", 114 | "required": false, 115 | "widget": "text", 116 | "attributes": [] 117 | } 118 | ] 119 | } 120 | ], 121 | "filters": [ 122 | { 123 | "name": "ymm", 124 | "label": "YMM", 125 | "type": 2, 126 | "required": false, 127 | "attributes": [ 128 | { 129 | "name": "year", 130 | "label": "Year", 131 | "type": 0, 132 | "required": false, 133 | "attributes": [] 134 | }, 135 | { 136 | "name": "make", 137 | "label": "Make", 138 | "type": 1, 139 | "required": false, 140 | "attributes": [] 141 | }, 142 | { 143 | "name":"model", 144 | "label":"model", 145 | "type": 1, 146 | "required": false, 147 | "attributes": [] 148 | } 149 | ], 150 | "widget": "ymm_selector" 151 | } 152 | ] 153 | } 154 | ] 155 | {{end}} -------------------------------------------------------------------------------- /api/gov/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "gov", 5 | embed = [":gov_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/gov", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "gov_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/api/gov", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/gov", 20 | "//lib/utils", 21 | "@com_github_aws_aws_lambda_go//events", 22 | "@com_github_aws_aws_lambda_go//lambda", 23 | "@com_github_aws_aws_sdk_go//aws", 24 | "@com_github_aws_aws_sdk_go//aws/session", 25 | "@com_github_aws_aws_sdk_go//service/lambda", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /api/gov/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "goclassifieds/lib/gov" 6 | "goclassifieds/lib/utils" 7 | "log" 8 | "os" 9 | 10 | "github.com/aws/aws-lambda-go/events" 11 | "github.com/aws/aws-lambda-go/lambda" 12 | "github.com/aws/aws-sdk-go/aws" 13 | "github.com/aws/aws-sdk-go/aws/session" 14 | lambda2 "github.com/aws/aws-sdk-go/service/lambda" 15 | ) 16 | 17 | var handler Handler 18 | 19 | type Handler func(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) 20 | 21 | type ActionContext struct { 22 | Stage string 23 | Lambda *lambda2.Lambda 24 | } 25 | 26 | func GetGrant(req *events.APIGatewayProxyRequest, ac *ActionContext) (events.APIGatewayProxyResponse, error) { 27 | var res events.APIGatewayProxyResponse 28 | 29 | grantAccessRequest := gov.GrantAccessRequest{ 30 | User: req.PathParameters["user"], 31 | Type: gov.UserTypeMap[req.PathParameters["type"]], 32 | Resource: gov.ResourceTypeMap[req.PathParameters["resource"]], 33 | Operation: gov.OperationMap[req.PathParameters["op"]], 34 | Asset: req.PathParameters["proxy"], 35 | } 36 | 37 | log.Print(req) 38 | 39 | payload, err := json.Marshal(grantAccessRequest) 40 | if err != nil { 41 | log.Printf("Error marshalling grant access request: %s", err.Error()) 42 | res.StatusCode = 500 43 | return res, err 44 | } 45 | 46 | lambdaRes, err := ac.Lambda.Invoke(&lambda2.InvokeInput{FunctionName: aws.String("goclassifieds-api-" + ac.Stage + "-GrantAccess"), Payload: payload}) 47 | if err != nil { 48 | log.Printf("error invoking grant_access: %s", err.Error()) 49 | res.StatusCode = 500 50 | return res, err 51 | } 52 | 53 | //var grantRes gov.GrantAccessResponse 54 | //json.Unmarshal(lambdaRes.Payload, &grantRes) 55 | 56 | res.Body = string(lambdaRes.Payload) 57 | res.StatusCode = 200 58 | return res, nil 59 | } 60 | 61 | func InitializeHandler(c *ActionContext) Handler { 62 | return func(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { 63 | 64 | usageLog := &utils.LogUsageLambdaInput{ 65 | UserId: req.PathParameters["user"], 66 | Username: "null", 67 | Resource: req.Resource, 68 | Path: req.Path, 69 | RequestId: req.RequestContext.RequestID, 70 | Intensities: "null", 71 | Regions: "null", 72 | Region: "null", 73 | Service: "null", 74 | Repository: "null", 75 | Organization: "null", 76 | } 77 | _, hedged := req.Headers["x-hedge-region"] 78 | if hedged { 79 | usageLog.Intensities = req.Headers["x-hedge-intensities"] 80 | usageLog.Regions = req.Headers["x-hedge-regions"] 81 | usageLog.Region = req.Headers["x-hedge-region"] 82 | usageLog.Service = req.Headers["x-hedge-service"] 83 | } 84 | _, hasOwner := req.PathParameters["owner"] 85 | if hasOwner { 86 | usageLog.Organization = req.PathParameters["owner"] 87 | } 88 | _, hasRepo := req.PathParameters["repo"] 89 | if hasRepo { 90 | usageLog.Repository = req.PathParameters["repo"] 91 | } 92 | 93 | utils.LogUsageForLambdaWithInput(usageLog) 94 | 95 | ac := RequestActionContext(c) 96 | 97 | //ac.UserId = GetUserId(req) 98 | 99 | if req.HTTPMethod == "GET" { 100 | return GetGrant(req, ac) 101 | } /*else if entityName == pluralName && req.HTTPMethod == "GET" { 102 | return GetEntities(req, ac) 103 | }*/ 104 | 105 | return events.APIGatewayProxyResponse{StatusCode: 400}, nil 106 | } 107 | } 108 | 109 | func RequestActionContext(c *ActionContext) *ActionContext { 110 | 111 | ac := &ActionContext{ 112 | Stage: c.Stage, 113 | Lambda: c.Lambda, 114 | } 115 | 116 | return ac 117 | 118 | } 119 | 120 | func init() { 121 | log.Printf("gov start") 122 | 123 | sess := session.Must(session.NewSession()) 124 | lClient := lambda2.New(sess) 125 | //gateway := apigatewaymanagementapi.New(sess, aws.NewConfig().WithEndpoint(os.Getenv("APIGATEWAY_ENDPOINT"))) 126 | 127 | actionContext := ActionContext{ 128 | Stage: os.Getenv("STAGE"), 129 | Lambda: lClient, 130 | } 131 | 132 | handler = InitializeHandler(&actionContext) 133 | 134 | log.Print("gov started") 135 | } 136 | 137 | func main() { 138 | log.SetFlags(0) 139 | lambda.Start(handler) 140 | } 141 | -------------------------------------------------------------------------------- /api/media/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "media", 5 | embed = [":media_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/media", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "media_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/media", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "//lib/repo", 25 | "//lib/gov", 26 | "@com_github_aws_aws_lambda_go//events", 27 | "@com_github_aws_aws_lambda_go//lambda", 28 | "@com_github_aws_aws_sdk_go//aws", 29 | "@com_github_aws_aws_sdk_go//aws/session", 30 | "@com_github_aws_aws_sdk_go//service/s3", 31 | "@com_github_aws_aws_sdk_go//service/s3/s3manager", 32 | "@org_golang_x_oauth2//:go_default_library", 33 | "@com_github_shurcool_githubv4//:go_default_library", 34 | "@com_github_google_go_github_v46//github", 35 | "@com_github_aws_aws_sdk_go//service/lambda", 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /api/proxy/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "proxy", 5 | embed = [":proxy_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/proxy", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "proxy_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/proxy", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/utils", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /api/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "goclassifieds/lib/utils" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "strings" 11 | 12 | "github.com/aws/aws-lambda-go/events" 13 | "github.com/aws/aws-lambda-go/lambda" 14 | ) 15 | 16 | var aveDomain string 17 | var aveApiKey string 18 | var carbonAwareDomain string 19 | 20 | func GetCities(country string, state string, city string) (string, error) { 21 | res, err := http.Get("http://api.zippopotam.us/" + country + "/" + state + "/" + city) 22 | if err != nil { 23 | return "", err 24 | } 25 | body, _ := ioutil.ReadAll(res.Body) 26 | return string(body), nil 27 | } 28 | 29 | func GetRequest(domain string, req *events.APIGatewayProxyRequest) (string, error) { 30 | qs := make([]string, len(req.QueryStringParameters)) 31 | i := 0 32 | for k, v := range req.QueryStringParameters { 33 | qs[i] = url.QueryEscape(k) + "=" + url.QueryEscape(v) 34 | i++ 35 | } 36 | var res *http.Response 37 | var err error 38 | var uri string 39 | if strings.Index(req.Path, "carbonaware") > -1 { 40 | uri = "https://" + domain + "/" + req.PathParameters["proxy"] + "?" + strings.Join(qs, "&") 41 | log.Print(uri) 42 | res, err = http.Get(uri) 43 | } else { 44 | res, err = http.Get("https://" + domain + "/query?apikey=" + aveApiKey + "&" + strings.Join(qs, "&")) 45 | } 46 | if err != nil { 47 | return "", err 48 | } 49 | body, _ := ioutil.ReadAll(res.Body) 50 | return string(body), nil 51 | } 52 | 53 | func ProxyRequest(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { 54 | 55 | usageLog := &utils.LogUsageLambdaInput{ 56 | // UserId: GetUserId(req), 57 | //Username: GetUsername(req), 58 | UserId: "null", 59 | Username: "null", 60 | Resource: req.Resource, 61 | Path: req.Path, 62 | RequestId: req.RequestContext.RequestID, 63 | Intensities: "null", 64 | Regions: "null", 65 | Region: "null", 66 | Service: "null", 67 | Repository: "null", 68 | Organization: "null", 69 | } 70 | _, hedged := req.Headers["x-hedge-region"] 71 | if hedged { 72 | usageLog.Intensities = req.Headers["x-hedge-intensities"] 73 | usageLog.Regions = req.Headers["x-hedge-regions"] 74 | usageLog.Region = req.Headers["x-hedge-region"] 75 | usageLog.Service = req.Headers["x-hedge-service"] 76 | } 77 | 78 | utils.LogUsageForLambdaWithInput(usageLog) 79 | 80 | if strings.Index(req.Path, "cities") > -1 { 81 | body, err := GetCities(req.PathParameters["country"], req.PathParameters["state"], req.PathParameters["city"]) 82 | if err != nil { 83 | return events.APIGatewayProxyResponse{StatusCode: 500}, err 84 | } 85 | return events.APIGatewayProxyResponse{StatusCode: 200, Body: body, Headers: map[string]string{"Content-Type": "application/json"}}, nil 86 | } else if strings.Index(req.Path, "ave") > -1 { 87 | body, err := GetRequest(aveDomain, req) 88 | if err != nil { 89 | return events.APIGatewayProxyResponse{StatusCode: 500}, err 90 | } 91 | return events.APIGatewayProxyResponse{StatusCode: 200, Body: body, Headers: map[string]string{"Content-Type": "application/json"}}, nil 92 | } else if strings.Index(req.Path, "carbonaware") > -1 { 93 | body, err := GetRequest(carbonAwareDomain, req) 94 | if err != nil { 95 | return events.APIGatewayProxyResponse{StatusCode: 500}, err 96 | } 97 | return events.APIGatewayProxyResponse{StatusCode: 200, Body: body, Headers: map[string]string{"Content-Type": "application/json"}}, nil 98 | } 99 | return events.APIGatewayProxyResponse{StatusCode: 400}, nil 100 | } 101 | 102 | func main() { 103 | log.SetFlags(0) 104 | aveDomain = os.Getenv("PROXY_AVE_DOMAIN") 105 | aveApiKey = os.Getenv("PROXY_AVE_APIKEY") 106 | carbonAwareDomain = os.Getenv("PROXY_CARBONAWARE_DOMAIN") 107 | lambda.Start(ProxyRequest) 108 | } 109 | -------------------------------------------------------------------------------- /api/proxy/test_data/cities.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": "/cities/{country}/{state}/{city}", 3 | "path": "/cities/us/wa/seattle", 4 | "httpMethod": "GET", 5 | "headers": { 6 | "Content-Length": "625", 7 | "Content-Type": "application/json", 8 | "Host": "p1vgub4jtb.execute-api.us-east-1.amazonaws.com", 9 | "Postman-Token": "71cbea13-de45-4160-b91c-2695baf650e2", 10 | "User-Agent": "PostmanRuntime/6.4.1", 11 | "X-Amzn-Trace-Id": "Root=1-5ec7a5d9-e77a95ff2fc7420140ab5832", 12 | "X-Forwarded-For": "75.118.234.96", 13 | "X-Forwarded-Port": "443", 14 | "X-Forwarded-Proto": "https", 15 | "accept": "*/*", 16 | "accept-encoding": "gzip, deflate", 17 | "authorization": "Bearer eyJraWQiOiJVTlRrNGhWaFVycTBrOHVBR3ZEWXNcL3cwZ1wvaVcrYjlQd2VPYk1iTTZTXC93PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlMzZiNDJmZS1iMDljLTQ1MTQtYTUxOS1lMTc4YmI1Mjk1N2UiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIGFkc19hcGlcL2Fkc19hcGkgbWVkaWFfYXBpXC9tZWRpYV9hcGkgb3BlbmlkIHByb2ZpbGUgdGF4b25vbXlfYXBpXC90YXhvbm9teV9hcGkgcHJvZmlsZXNfYXBpXC9wcm9maWxlc19hcGkgY2hhdFwvY2hhdCIsImF1dGhfdGltZSI6MTU5MDUwNzk1NiwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfejhQaEszRDhWIiwiZXhwIjoxNTkwNTExNTU2LCJpYXQiOjE1OTA1MDc5NTYsInZlcnNpb24iOjIsImp0aSI6ImFlZTY0Yzg1LTUxYWUtNDliNC1hZGY5LTZlMTBkMTc1ZTkxYiIsImNsaWVudF9pZCI6IjNsa2lkbjc5cTRqcThuYWhrZWNzZ3VsZWlqIiwidXNlcm5hbWUiOiJ0b2RkeiJ9.O3kdN7_FZiPwBo7OGecOsBRdrE13qASe35f2pg12qLbCyVwqO4aiPmQX82NxgAoEHxs6Arq3sTkORfeCTf5pUzgEIM-9KhDaVSbUdje4Wge-KezheRVY9i1ssxvq02_8D_5gY7Ak_K7PnEV_cblF3szGeQ6xtYw-5WIbxExn6aBK0OBSCvzqEc0lY6laP3Kjjq7eqAkkFsigiFmbDrrAStYCZbtId-tYiDv2wdGDu9QPX7zhlv8xbyTHlxRDJNR39e8WQcwVn38YCHbJoFhAqRP3NUKTzFYPAA4Ta4prY1tfmtKT3jWGqCcvk4_fmMnLQXsbvU1p-mPqdmcldViNnw", 18 | "cache-control": "no-cache" 19 | }, 20 | "multiValueHeaders": { 21 | "Content-Length": [ 22 | "625" 23 | ], 24 | "Content-Type": [ 25 | "application/json" 26 | ], 27 | "Host": [ 28 | "p1vgub4jtb.execute-api.us-east-1.amazonaws.com" 29 | ], 30 | "Postman-Token": [ 31 | "71cbea13-de45-4160-b91c-2695baf650e2" 32 | ], 33 | "User-Agent": [ 34 | "PostmanRuntime/6.4.1" 35 | ], 36 | "X-Amzn-Trace-Id": [ 37 | "Root=1-5ec7a5d9-e77a95ff2fc7420140ab5832" 38 | ], 39 | "X-Forwarded-For": [ 40 | "75.118.234.96" 41 | ], 42 | "X-Forwarded-Port": [ 43 | "443" 44 | ], 45 | "X-Forwarded-Proto": [ 46 | "https" 47 | ], 48 | "accept": [ 49 | "*/*" 50 | ], 51 | "accept-encoding": [ 52 | "gzip, deflate" 53 | ], 54 | "authorization": [ 55 | "Bearer eyJraWQiOiJVTlRrNGhWaFVycTBrOHVBR3ZEWXNcL3cwZ1wvaVcrYjlQd2VPYk1iTTZTXC93PSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJlMzZiNDJmZS1iMDljLTQ1MTQtYTUxOS1lMTc4YmI1Mjk1N2UiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIGFkc19hcGlcL2Fkc19hcGkgbWVkaWFfYXBpXC9tZWRpYV9hcGkgb3BlbmlkIHByb2ZpbGUgdGF4b25vbXlfYXBpXC90YXhvbm9teV9hcGkgcHJvZmlsZXNfYXBpXC9wcm9maWxlc19hcGkgY2hhdFwvY2hhdCIsImF1dGhfdGltZSI6MTU5MDE0MTQ1MCwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLWVhc3QtMS5hbWF6b25hd3MuY29tXC91cy1lYXN0LTFfejhQaEszRDhWIiwiZXhwIjoxNTkwMTQ1MDUwLCJpYXQiOjE1OTAxNDE0NTAsInZlcnNpb24iOjIsImp0aSI6IjQ4OWVkMTNhLTM5ZDUtNDcxMS1iNTI1LTllNTlhODdjYTM2MiIsImNsaWVudF9pZCI6IjNsa2lkbjc5cTRqcThuYWhrZWNzZ3VsZWlqIiwidXNlcm5hbWUiOiJ0b2RkeiJ9.IhzKi4MilXOArmX75VmIhRlVSqYQl_0wLibWETAukwcAnp_Zl2I6_p8ryA9wEudjqXFBpRcZa2wlZh2ZZ4Jd8nuFq9PcwD5WBTTeQ0dSlHQ6ls9FFSigqUW-w62JM1RwI52ImU8G_ZvVJlX9Bc6UbRlT6BzLVcJMco4k3oSWoU9mYbn-u2KBQ9x2SJEH6YHBmOJxgty5jo_Q1xPHTRjnY9lNI3oVOUCIbIjHEcw9Kj5MMeWC4zHmxhaae6gO1GMI9RMydpnqL1AJ2jpWXgJDyudDTGIACtryBZKlVdGhq-dbfURTvarKGNodrJGE9do3zZF7NbraSgS7DnJKv5bv7g" 56 | ], 57 | "cache-control": [ 58 | "no-cache" 59 | ] 60 | }, 61 | "queryStringParameters": {}, 62 | "multiValueQueryStringParameters": null, 63 | "pathParameters": { 64 | "country": "us", 65 | "city": "seattle", 66 | "state": "wa" 67 | }, 68 | "stageVariables": null, 69 | "requestContext": { 70 | "accountId": "989992233821", 71 | "resourceId": "GET /cities/{country}/{state}/{city}", 72 | "stage": "$default", 73 | "requestId": "M7bZ9hnfoAMEVcw=", 74 | "identity": { 75 | "cognitoIdentityPoolId": "", 76 | "accountId": "", 77 | "cognitoIdentityId": "", 78 | "caller": "", 79 | "apiKey": "", 80 | "apiKeyId": "", 81 | "accessKey": "", 82 | "sourceIp": "75.118.234.96", 83 | "cognitoAuthenticationType": "", 84 | "cognitoAuthenticationProvider": "", 85 | "userArn": "", 86 | "userAgent": "PostmanRuntime/6.4.1", 87 | "user": "" 88 | }, 89 | "resourcePath": "/cities/us/wa/seattle", 90 | "authorizer": {}, 91 | "httpMethod": "GET", 92 | "apiId": "p1vgub4jtb" 93 | }, 94 | "body": "" 95 | } -------------------------------------------------------------------------------- /api/stream/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "stream", 5 | embed = [":stream_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/stream", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "stream_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/stream", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/entity", 24 | "//lib/utils", 25 | "@com_github_aws_aws_lambda_go//events", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_aws_aws_sdk_go//aws/session", 28 | "@com_github_aws_aws_sdk_go//service/lambda", 29 | "@com_github_gocql_gocql//:gocql", 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /api/stream/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "goclassifieds/lib/entity" 8 | "goclassifieds/lib/utils" 9 | "log" 10 | "os" 11 | 12 | "github.com/aws/aws-lambda-go/events" 13 | "github.com/aws/aws-lambda-go/lambda" 14 | session "github.com/aws/aws-sdk-go/aws/session" 15 | lambda2 "github.com/aws/aws-sdk-go/service/lambda" 16 | "github.com/gocql/gocql" 17 | ) 18 | 19 | var handler Handler 20 | 21 | type Handler func(req *events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) 22 | 23 | type ActionContext struct { 24 | Session *gocql.Session 25 | Lambda *lambda2.Lambda 26 | UserId string 27 | Stage string 28 | ConnManager entity.EntityManager 29 | } 30 | 31 | func Connect(req *events.APIGatewayWebsocketProxyRequest, ac *ActionContext) (events.APIGatewayProxyResponse, error) { 32 | log.Print("connect") 33 | //log.Print("user id = " + ac.UserId) 34 | obj := make(map[string]interface{}) 35 | obj["connId"] = req.RequestContext.ConnectionID 36 | _, err := ac.ConnManager.Create(obj) 37 | if err != nil { 38 | log.Print(err) 39 | return events.APIGatewayProxyResponse{StatusCode: 500}, err 40 | } 41 | return events.APIGatewayProxyResponse{StatusCode: 200}, nil 42 | } 43 | 44 | func Disconnect(req *events.APIGatewayWebsocketProxyRequest, ac *ActionContext) (events.APIGatewayProxyResponse, error) { 45 | log.Print("disconnect") 46 | obj := make(map[string]interface{}) 47 | obj["connId"] = req.RequestContext.ConnectionID 48 | ac.ConnManager.Purge("default", obj) 49 | return events.APIGatewayProxyResponse{StatusCode: 200}, nil 50 | } 51 | 52 | func InitializeHandler(c *ActionContext) Handler { 53 | return func(req *events.APIGatewayWebsocketProxyRequest) (events.APIGatewayProxyResponse, error) { 54 | 55 | usageLog := &utils.LogUsageLambdaInput{ 56 | UserId: GetUserId(req), 57 | //Username: GetUsername(req), 58 | Username: "null", 59 | Resource: req.RequestContext.RouteKey, 60 | Path: req.RequestContext.EventType, 61 | RequestId: req.RequestContext.RequestID, 62 | Intensities: "null", 63 | Regions: "null", 64 | Region: "null", 65 | Service: "null", 66 | Repository: "null", 67 | Organization: "null", 68 | } 69 | _, hedged := req.Headers["x-hedge-region"] 70 | if hedged { 71 | usageLog.Intensities = req.Headers["x-hedge-intensities"] 72 | usageLog.Regions = req.Headers["x-hedge-regions"] 73 | usageLog.Region = req.Headers["x-hedge-region"] 74 | usageLog.Service = req.Headers["x-hedge-service"] 75 | } 76 | /*_, hasOwner := req.PathParameters["owner"] 77 | if hasOwner { 78 | usageLog.Organization = req.PathParameters["owner"] 79 | } 80 | _, hasRepo := req.PathParameters["repo"] 81 | if hasRepo { 82 | usageLog.Repository = req.PathParameters["repo"] 83 | }*/ 84 | 85 | utils.LogUsageForLambdaWithInput(usageLog) 86 | 87 | ac := RequestActionContext(c) 88 | 89 | ac.UserId = GetUserId(req) 90 | ac.ConnManager = CreateConnectionManager(ac) 91 | ac.ConnManager.Config.LogUsageLambdaInput = usageLog 92 | 93 | b, _ := json.Marshal(req) 94 | log.Print(string(b)) 95 | 96 | if req.RequestContext.RouteKey == "$connect" { 97 | return Connect(req, ac) 98 | } else if req.RequestContext.RouteKey == "$disconnect" { 99 | return Disconnect(req, ac) 100 | } else if req.RequestContext.RouteKey == "$default" { 101 | return events.APIGatewayProxyResponse{StatusCode: 200}, nil 102 | } 103 | 104 | return events.APIGatewayProxyResponse{StatusCode: 500}, nil 105 | } 106 | } 107 | 108 | func CreateConnectionManager(ac *ActionContext) entity.EntityManager { 109 | manager := entity.EntityManager{ 110 | Config: entity.EntityConfig{ 111 | SingularName: "chatconnection", 112 | PluralName: "chatconnections", 113 | IdKey: "connId", 114 | Stage: ac.Stage, 115 | }, 116 | Creator: entity.DefaultCreatorAdaptor{ 117 | Config: entity.DefaultCreatorConfig{ 118 | Lambda: ac.Lambda, 119 | UserId: ac.UserId, 120 | Save: "default", 121 | }, 122 | }, 123 | Storages: map[string]entity.Storage{ 124 | "default": entity.CqlStorageAdaptor{ 125 | Config: entity.CqlAdaptorConfig{ 126 | Session: ac.Session, 127 | Table: "chatconnections", 128 | }, 129 | }, 130 | }, 131 | Authorizers: map[string]entity.Authorization{ 132 | "default": entity.NoopAuthorizationAdaptor{}, 133 | }, 134 | } 135 | return manager 136 | } 137 | 138 | func RequestActionContext(c *ActionContext) *ActionContext { 139 | return &ActionContext{ 140 | Session: c.Session, 141 | Lambda: c.Lambda, 142 | Stage: c.Stage, 143 | } 144 | } 145 | 146 | func GetUserId(req *events.APIGatewayWebsocketProxyRequest) string { 147 | userId := "" 148 | if req.RequestContext.Authorizer.(map[string]interface{})["sub"] != nil { 149 | userId = fmt.Sprint(req.RequestContext.Authorizer.(map[string]interface{})["sub"]) 150 | if userId == "" { 151 | userId = "" 152 | } 153 | } 154 | return userId 155 | } 156 | 157 | func init() { 158 | log.Print("init") 159 | cluster := gocql.NewCluster("cassandra.us-east-1.amazonaws.com") 160 | cluster.Keyspace = "ClassifiedsDev" 161 | cluster.Port = 9142 162 | cluster.Consistency = gocql.LocalQuorum 163 | cluster.Authenticator = &gocql.PasswordAuthenticator{Username: os.Getenv("KEYSPACE_USERNAME"), Password: os.Getenv("KEYSPACE_PASSWORD")} 164 | cluster.SslOpts = &gocql.SslOptions{Config: &tls.Config{ServerName: "cassandra.us-east-1.amazonaws.com"}, CaPath: "api/chat/AmazonRootCA1.pem", EnableHostVerification: true} 165 | cluster.PoolConfig = gocql.PoolConfig{HostSelectionPolicy: /*gocql.TokenAwareHostPolicy(*/ gocql.DCAwareRoundRobinPolicy("us-east-1") /*)*/} 166 | cSession, err := cluster.CreateSession() 167 | if err != nil { 168 | log.Fatal(err) 169 | } 170 | 171 | sess := session.Must(session.NewSession()) 172 | lClient := lambda2.New(sess) 173 | 174 | actionContext := ActionContext{ 175 | Session: cSession, 176 | Lambda: lClient, 177 | Stage: os.Getenv("STAGE"), 178 | } 179 | 180 | handler = InitializeHandler(&actionContext) 181 | } 182 | 183 | func main() { 184 | log.SetFlags(0) 185 | log.Print("start xxx") 186 | lambda.Start(handler) 187 | } 188 | -------------------------------------------------------------------------------- /api/user/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "user", 5 | embed = [":user_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/user", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | #go_test( 13 | # name = "mainTest", 14 | # srcs = ["main_test.go"], 15 | #) 16 | 17 | go_library( 18 | name = "user_lib", 19 | srcs = ["main.go"], 20 | importpath = "goclassifieds/api/user", 21 | visibility = ["//visibility:private"], 22 | deps = [ 23 | "//lib/entity", 24 | "//lib/utils", 25 | "@com_github_aws_aws_lambda_go//events", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_aws_aws_sdk_go//aws/session", 28 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 29 | "@com_github_aws_aws_sdk_go//service/ses", 30 | "@org_golang_x_oauth2//:go_default_library", 31 | "@com_github_google_go_github_v46//github", 32 | "@com_github_aws_aws_sdk_go//aws", 33 | "@com_github_sethvargo_go_password//password", 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /api/versait/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "versait", 5 | embed = [":versait_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/api/versait", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "versait_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/api/versait", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "@com_github_aws_aws_lambda_go//events", 20 | "@com_github_aws_aws_lambda_go//lambda", 21 | ], 22 | ) 23 | -------------------------------------------------------------------------------- /app/image_feature/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollthecloudinc/hedge/5c8fca87dcdc72b61fd1faba2eab0d46d67d73c0/app/image_feature/BUILD.bazel -------------------------------------------------------------------------------- /app/knn_spherical/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "knn_spherical", 5 | embed = [":knn_spherical_app"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/app/knn_spherical", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "knn_spherical_app", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/app/knn_spherical", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | ], 20 | ) 21 | -------------------------------------------------------------------------------- /architecture/commands.md: -------------------------------------------------------------------------------- 1 | dev: serverless deploy 2 | prod: serverless deploy --aws-profile prod --stage prod -------------------------------------------------------------------------------- /architecture/plugins.md: -------------------------------------------------------------------------------- 1 | Call functions if they exists at certain points to alter -------------------------------------------------------------------------------- /architecture/sites.md: -------------------------------------------------------------------------------- 1 | Add Site key to top level entities like panel page. 2 | 3 | This could be an array allowing opne piece of content to belong to multiple sites. 4 | 5 | Include site id in query string when applicable. 6 | 7 | Limit result set to items in that site. 8 | 9 | configs 10 | -------- 11 | 12 | does this matter not - not really 13 | 14 | all sites share same resources for now. 15 | 16 | 17 | 18 | ----------- 19 | 20 | environments (limited - lambda limit) 21 | 22 | - dev 23 | - prod 24 | 25 | ------ 26 | 27 | restrict write on entities (pages) user is not allowed to write 0- entity permissions 28 | 29 | * New Authorizer that can be used with entity permissions on entities -------------------------------------------------------------------------------- /cloudform/bucket.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: 'This template sets up bucket to host automation resources.' 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: The name of the environment (e.g., dev, prod) 7 | Type: String 8 | VendorSuffix: 9 | Description: Unique vendor suffix for the bucket. 10 | Type: String 11 | 12 | Resources: 13 | CloudformationTemplatesBucket: 14 | Type: 'AWS::S3::Bucket' 15 | Properties: 16 | BucketName: !Sub 'rtc-classifieds-cloudformation-templates-${EnvironmentName}-${VendorSuffix}' 17 | AccessControl: Private 18 | BucketEncryption: 19 | ServerSideEncryptionConfiguration: 20 | - ServerSideEncryptionByDefault: 21 | SSEAlgorithm: AES256 -------------------------------------------------------------------------------- /cloudform/main.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: 'AWS CloudFormation Sample Template: This template automates ase infrastructure.' 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: The name of the environment (e.g., dev, prod) 7 | Type: String 8 | EnvironmentNameCamelCase: 9 | Description: The name of the environment (e.g., dev, prod) in CamelCase for supporting multiple environments in same account without conflicts. 10 | Type: String 11 | VendorSuffix: 12 | Description: Unique vendor suffix for the bucket. 13 | Type: String 14 | VendorSuffixCamelCase: 15 | Description: Unique vendor suffix in CamelCase for supporting multiple vendors under the same account. 16 | Type: String 17 | SSODomain: 18 | Description: The domain for hosting sso page. 19 | Type: String 20 | 21 | Resources: 22 | UsersStack: 23 | Type: AWS::CloudFormation::Stack 24 | Properties: 25 | TemplateURL: !Sub 'https://s3.amazonaws.com/rtc-classifieds-cloudformation-templates-${EnvironmentName}-${VendorSuffix}/users.yaml' 26 | Parameters: 27 | EnvironmentName: !Ref EnvironmentName 28 | EnvironmentNamCamelCase: !Ref EnvironmentNameCamelCase 29 | VendorSuffix: !Ref VendorSuffix 30 | VendorSuffixCamelCase: !Ref VendorSuffixCamelCase 31 | SSODomain: !Ref SSODomain 32 | 33 | SearchStack: 34 | Type: AWS::CloudFormation::Stack 35 | Properties: 36 | TemplateURL: !Sub 'https://s3.amazonaws.com/rtc-classifieds-cloudformation-templates-${EnvironmentName}-${VendorSuffix}/search.yaml' 37 | Parameters: 38 | EnvironmentName: !Ref EnvironmentName 39 | EnvironmentNamCamelCase: !Ref EnvironmentNameCamelCase 40 | VendorSuffix: !Ref VendorSuffix 41 | VendorSuffixCamelCase: !Ref VendorSuffixCamelCase 42 | UserPoolId: !GetAtt UsersStack.Outputs.UserPoolId 43 | IdentityPoolId: !GetAtt UsersStack.Outputs.IdentityPoolId -------------------------------------------------------------------------------- /cloudform/search.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define variables 4 | STACK_NAME="BaseSearchSystemStack" # Change this to your desired stack name 5 | TEMPLATE_FILE="search.yaml" # The name of the CloudFormation template file 6 | ENVIRONMENT_NAME="dev" # Replace with your environment name (e.g., dev, prod) 7 | ENVIRONMENT_NAME_CAMEL_CASE="Dev" # Replace with the CamelCase environment name (e.g., Dev, Prod) 8 | VENDOR_SUFFIX="rtc" # Replace with your vendor suffix 9 | VENDOR_SUFFIX_CAMEL_CASE="Rtc" # Replace with the CamelCase vendor suffix 10 | USER_POOL_ID="us-east-1_sWRV0kAgS" # Replace with your Cognito User Pool ID 11 | IDENTITY_POOL_ID="us-east-1:026caf18-c852-451b-a93e-fb431c4eee6d" # Replace with your Cognito Identity Pool ID 12 | REGION="us-east-1" 13 | 14 | # Check if AWS CLI is installed 15 | if ! command -v aws &> /dev/null; then 16 | echo "AWS CLI not found. Please install AWS CLI and configure it." 17 | exit 1 18 | fi 19 | 20 | 21 | 22 | # Deploy the CloudFormation stack 23 | echo "Deploying CloudFormation stack..." 24 | aws cloudformation deploy \ 25 | --stack-name "$STACK_NAME" \ 26 | --template-file "$TEMPLATE_FILE" \ 27 | --parameter-overrides \ 28 | EnvironmentName="$ENVIRONMENT_NAME" \ 29 | EnvironmentNameCamelCase="$ENVIRONMENT_NAME_CAMEL_CASE" \ 30 | VendorSuffix="$VENDOR_SUFFIX" \ 31 | VendorSuffixCamelCase="$VENDOR_SUFFIX_CAMEL_CASE" \ 32 | UserPoolId="$USER_POOL_ID" \ 33 | IdentityPoolId="$IDENTITY_POOL_ID" \ 34 | --capabilities CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND \ 35 | --region "$REGION" 36 | 37 | # Check the deployment status 38 | if [ $? -eq 0 ]; then 39 | echo "CloudFormation stack deployed successfully!" 40 | else 41 | echo "CloudFormation stack deployment failed." 42 | exit 1 43 | fi -------------------------------------------------------------------------------- /cloudform/search.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: 'AWS CloudFormation Sample Template: This template sets up the base search system.' 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: The name of the environment (e.g., dev, prod) 7 | Type: String 8 | EnvironmentNameCamelCase: 9 | Description: The name of the environment (e.g., dev, prod) in CamelCase for supporting multiple environments in same account without conflicts. 10 | Type: String 11 | VendorSuffix: 12 | Description: Unique vendor suffix for the bucket. 13 | Type: String 14 | VendorSuffixCamelCase: 15 | Description: Unique vendor suffix in CamelCase for supporting multiple vendors under the same account. 16 | Type: String 17 | UserPoolId: 18 | Description: The ID of the Cognito User Pool 19 | Type: String 20 | IdentityPoolId: 21 | Description: The ID of the Cognito Identity Pool 22 | Type: String 23 | 24 | Resources: 25 | CognitoOpenSearchRole: 26 | Type: "AWS::IAM::Role" 27 | Properties: 28 | RoleName: !Sub 'RtcCognitoOpenSearchRole${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 29 | AssumeRolePolicyDocument: 30 | Version: "2012-10-17" 31 | Statement: 32 | - Effect: "Allow" 33 | Principal: 34 | Federated: "cognito-identity.amazonaws.com" 35 | Action: 36 | - "sts:AssumeRole" 37 | Policies: 38 | - PolicyName: !Sub 'RtcCognitoOpenSearchAccess${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 39 | PolicyDocument: 40 | Version: "2012-10-17" 41 | Statement: 42 | - Effect: "Allow" 43 | Action: 44 | - "es:ESHttpGet" 45 | - "es:ESHttpPut" 46 | - "es:ESHttpPost" 47 | - "es:ESHttpDelete" 48 | Resource: !Sub 'arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/rtc-classifieds-${EnvironmentName}-${VendorSuffix}/*' 49 | 50 | MyDomain: 51 | Type: "AWS::OpenSearchService::Domain" 52 | Properties: 53 | DomainName: !Sub 'rtc-classifieds-${EnvironmentName}-${VendorSuffix}' 54 | EngineVersion: "OpenSearch_2.13" 55 | ClusterConfig: 56 | InstanceType: "t3.small.search" 57 | InstanceCount: 1 58 | EBSOptions: 59 | EBSEnabled: true 60 | VolumeSize: 10 61 | EncryptionAtRestOptions: 62 | Enabled: false 63 | NodeToNodeEncryptionOptions: 64 | Enabled: false 65 | DomainEndpointOptions: 66 | EnforceHTTPS: true 67 | TLSSecurityPolicy: "Policy-Min-TLS-1-2-2019-07" 68 | CognitoOptions: 69 | Enabled: true 70 | UserPoolId: !Ref UserPoolId 71 | IdentityPoolId: !Ref IdentityPoolId 72 | RoleArn: !GetAtt CognitoOpenSearchRole.Arn -------------------------------------------------------------------------------- /cloudform/users.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: 'AWS CloudFormation Sample Template: This template demonstrates the creation of Cognito User Pools' 3 | 4 | Parameters: 5 | EnvironmentName: 6 | Description: The name of the environment (e.g., dev, prod) 7 | Type: String 8 | EnvironmentNameCamelCase: 9 | Description: The name of the environment (e.g., dev, prod) in CamelCase for supporting multiple environments in same account without conflicts. 10 | Type: String 11 | VendorSuffix: 12 | Description: Unique vendor suffix for the bucket. 13 | Type: String 14 | VendorSuffixCamelCase: 15 | Description: Unique vendor suffix in CamelCase for supporting multiple vendors under the same account. 16 | Type: String 17 | SSODomain: 18 | Description: The domain for hosting sso page. 19 | Type: String 20 | 21 | Resources: 22 | UserPool: 23 | Type: "AWS::Cognito::UserPool" 24 | Properties: 25 | UserPoolName: !Sub 'RtcClassifiedsUserPool${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 26 | Policies: 27 | PasswordPolicy: 28 | MinimumLength: 8 29 | RequireLowercase: True 30 | RequireNumbers: True 31 | RequireSymbols: True 32 | RequireUppercase: True 33 | 34 | UserPoolClient: 35 | Type: "AWS::Cognito::UserPoolClient" 36 | Properties: 37 | ClientName: !Sub 'RtcClassifiedsUserPoolClient${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 38 | GenerateSecret: False 39 | UserPoolId: 40 | Ref: UserPool 41 | SupportedIdentityProviders: ["COGNITO"] 42 | CallbackURLs: [!Sub 'https://${SSODomain}/auth-callback'] 43 | LogoutURLs: [!Sub 'https://${SSODomain}/logout'] 44 | AllowedOAuthFlows: ["code"] 45 | AllowedOAuthScopes: ["email", "openid"] 46 | AllowedOAuthFlowsUserPoolClient: true 47 | 48 | UserPoolDomain: 49 | Type: "AWS::Cognito::UserPoolDomain" 50 | Properties: 51 | UserPoolId: 52 | Ref: UserPool 53 | Domain: !Ref SSODomain 54 | 55 | UserIdentityPool: 56 | Type: "AWS::Cognito::IdentityPool" 57 | Properties: 58 | IdentityPoolName: !Sub 'ClassifiedsIdentityPool${EnvironmentNameCamelCase}${VendorSuffixCamelCase}' 59 | AllowUnauthenticatedIdentities: false 60 | CognitoIdentityProviders: 61 | - ClientId: 62 | Ref: UserPoolClient 63 | ProviderName: 64 | Fn::GetAtt: 65 | - UserPool 66 | - ProviderName 67 | 68 | Outputs: 69 | UserPoolId: 70 | Description: The ID of the user pool 71 | Value: !Ref UserPool 72 | IdentityPoolId: 73 | Description: The ID of the identity pool 74 | Value: !Ref UserIdentityPool -------------------------------------------------------------------------------- /edge/renewable_redirect/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | 3 | esbuild( 4 | name = "renewable_redirect", 5 | entry_point = "index.js", 6 | platform = "node", 7 | target = "node10", 8 | tool = select({ 9 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 10 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 11 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 12 | }), 13 | deps = [ 14 | "@npm//matcher", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /edge/renewable_redirect/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const https = require('https'); 4 | const matcher = require('matcher'); 5 | 6 | const objectsCache = new Map(); 7 | 8 | exports.handler = async (event, _, callback) => { 9 | console.log('region', process.env.AWS_REGION); 10 | console.log('event', event); 11 | const request = event.Records[0].cf.request; 12 | console.log("REPORT " + /*"RequestId: " + req.RequestContext.RequestID + "*/ "Function: " + process.env.AWS_LAMBDA_FUNCTION_NAME + " Path: " + request.uri) 13 | const pieces = request.uri.split('/') 14 | // console.log('pieces', pieces); 15 | const uri = "/" + pieces.slice(2).join('/') 16 | const report = await getObject({ path: 'renewable-report/report' }); 17 | const service = await getObject({ path: 'services/' + pieces[1] }); 18 | const bestRegion = pickRegion({ service, report, uri }); 19 | const bestRegions = calculateBestRegions({ report }); 20 | provideFeedback({ bestRegions, bestRegion, report }); 21 | console.log('herexx'); 22 | delete request.origin.s3 23 | const customHeaders = {}; 24 | customHeaders["x-hedge-regions"] = [{ key: "x-hedge-regions", value: Object.keys(report.intensities).join(",") }]; 25 | customHeaders["x-hedge-intensities"] = [{ key: "x-hedge-intensities", value: Object.keys(report.intensities).map(r => report.intensities[r]).join(",") }]; 26 | customHeaders["x-hedge-region"] = [{ key: "x-hedge-region", value: bestRegion.region }]; 27 | customHeaders["x-hedge-service"] = [{ key: "x-hedge-service", value: pieces[1] }]; 28 | request.origin.custom = { 29 | domainName: bestRegion.origin, 30 | port: 443, 31 | protocol: "https", 32 | path: "", 33 | sslProtocols: ["TLSv1", "TLSv1.1", "TLSv1.2"], 34 | readTimeout: 30, 35 | keepaliveTimeout: 30, 36 | customHeaders 37 | }; 38 | request.uri = uri 39 | request.headers["host"] = [{key: "host", value: bestRegion.origin }]; 40 | request.headers['cache-control'] = [{key: "cache-control", value: 'no-cache'}]; 41 | console.log('new request', request); 42 | callback(null, request); 43 | }; 44 | 45 | function pickRegion({ service, report, uri }) { 46 | const availableRegions = service.regions.filter(r => filterRegions({ region: r, uri })).map(r => r.region); 47 | const bestRegions = calculateBestRegions({ report }); 48 | let bestAvailableRegion = pickBestAvailableRegion({ availableRegions, bestRegions }); 49 | if (bestAvailableRegion === undefined) { 50 | bestAvailableRegion = report.defaultRegion !== undefined ? report.defaultRegion : 'eastus'; 51 | } 52 | const bestServiceRegion = service.regions.find(r => r.region === bestAvailableRegion); 53 | console.log('pickRegion', 'best service region', bestServiceRegion.region); 54 | console.log('pickRegion', 'picked region intensity', report.intensities[bestAvailableRegion]); 55 | return { origin: bestServiceRegion.origin, region: bestServiceRegion.region }; 56 | } 57 | 58 | async function getObject({ path }) { 59 | const stage = process.env.STAGE; 60 | let domain = 'store.hedge.earth'; 61 | let pathPrefix = '/'; 62 | //console.log('vars', process.env); 63 | console.log('stage', stage); 64 | if (stage === 'dev' /*|| stage === undefined*/) { 65 | domain = "rollthecloudinc.github.io"; 66 | pathPrefix = "/hedge-objects/"; 67 | } 68 | const options = { host: domain, path: pathPrefix + path + '.json' }; 69 | console.log('getObject', "options", options); 70 | return objectsCache.has(path) ? new Promise(res => res(objectsCache.get(path))) : new Promise(resolve => { 71 | https.request(options, res => { 72 | var str = ''; 73 | res.on('data', chunk => str += chunk); 74 | res.on('end', () => { 75 | console.log('getObject', "path", str); 76 | const obj = JSON.parse(str); 77 | objectsCache.set(path, obj); 78 | resolve(obj); 79 | }); 80 | }).end(); 81 | }); 82 | } 83 | 84 | function calculateBestRegions({ report }) { 85 | const intensitiesKeys = Object.keys(report.intensities); 86 | const bestRegions = []; 87 | intensitiesKeys.forEach(region => { 88 | if (report.intensities[region] > 0) { 89 | bestRegions.push({ intensity: report.intensities[region], region }); 90 | } else { 91 | console.log('pickRegion', 'toss', region); 92 | } 93 | }); 94 | bestRegions.sort((a, b) => a.intensity > b.intensity ? 1 : -1); 95 | console.log('calculateBestRegions', 'best regions', bestRegions); 96 | return bestRegions; 97 | } 98 | 99 | function pickBestAvailableRegion({ bestRegions, availableRegions }) { 100 | let bestAvailableRegion 101 | const len = bestRegions.length; 102 | for (let i = 0; i < len; i++) { 103 | const matchedAvailabilityRegion = availableRegions.find(region => region === bestRegions[i].region); 104 | if (matchedAvailabilityRegion !== undefined) { 105 | bestAvailableRegion = matchedAvailabilityRegion; 106 | break; 107 | } 108 | } 109 | console.log('pickBestAvailableRegion', 'best region', bestAvailableRegion); 110 | return bestAvailableRegion; 111 | } 112 | 113 | function provideFeedback({ bestRegions, bestRegion, report }) { 114 | console.log("provideFeedbackx") 115 | if (bestRegions.length > 0 && bestRegions[0].region === bestRegion.region) { 116 | console.log('Using region [' + bestRegion.region + '] with absolute lowest carbon intentity.'); 117 | } else if (bestRegions.length > 0 && bestRegions.find(r => r.region === bestRegion.region) !== undefined) { 118 | console.log('Using less optimal region [' + bestRegion.region + ']. If you were using [' + bestRegions[0].region + '] could have saved an extra ' + (report.intensities[bestRegion.region] - report.intensities[bestRegions[0].region]) + ' of carbon.'); 119 | } else { 120 | cobnsole.log('Using region [' + bestRegion.region + '] no intensity grid data available.'); 121 | } 122 | // @todo: default region difference. - carbon savings. 123 | } 124 | 125 | function filterRegions({ region, uri }) { 126 | return typeof(region.paths) === 'undefined' || region.paths.length === 0 || region.paths.filter(path => matcher.isMatch(uri ,path)).length > 0 127 | } -------------------------------------------------------------------------------- /email_templates/mytemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "Template": { 3 | "TemplateName": "MyTemplate", 4 | "SubjectPart": "Greetings, {{name}}!", 5 | "HtmlPart": "

Hello {{name}},

Your favorite animal is {{favoriteanimal}}.

", 6 | "TextPart": "Dear {{name}},\r\nYour favorite animal is {{favoriteanimal}}." 7 | } 8 | } -------------------------------------------------------------------------------- /email_templates/temp_password.json: -------------------------------------------------------------------------------- 1 | { 2 | "Template": { 3 | "TemplateName": "TempPassword", 4 | "SubjectPart": "Greetings, {{name}}!", 5 | "HtmlPart": "

Hello {{name}},

Your temp password is {{tempPassword}}

", 6 | "TextPart": "Dear {{name}},\r\nYour temp password is {{tempPassword}}" 7 | } 8 | } -------------------------------------------------------------------------------- /func/azure_hedge/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "azure_hedge", 5 | embed = [":azure_hedge_lib"], 6 | goarch = "amd64", 7 | goos = "windows", 8 | importpath = "goclassifieds/func/azure_hedge", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "azure_hedge_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/azure_hedge", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/shapeshift", 19 | "@com_github_aws_aws_lambda_go//events", 20 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 21 | "@com_github_micahparks_keyfunc//:go_default_library" 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /func/convert_media/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "convert_media", 5 | embed = [":convert_media_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/convert_media", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "convert_media_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/convert_media", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "@com_github_aws_aws_lambda_go//events", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | "@com_github_aws_aws_sdk_go//aws", 21 | "@com_github_aws_aws_sdk_go//aws/session", 22 | "@com_github_aws_aws_sdk_go//service/lambda", 23 | "@com_github_aws_aws_sdk_go//service/s3", 24 | "@com_github_aws_aws_sdk_go//service/s3/s3manager", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /func/convert_media/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "log" 9 | "os" 10 | 11 | "github.com/aws/aws-lambda-go/events" 12 | "github.com/aws/aws-lambda-go/lambda" 13 | "github.com/aws/aws-sdk-go/aws" 14 | "github.com/aws/aws-sdk-go/aws/session" 15 | lambda2 "github.com/aws/aws-sdk-go/service/lambda" 16 | "github.com/aws/aws-sdk-go/service/s3" 17 | "github.com/aws/aws-sdk-go/service/s3/s3manager" 18 | ) 19 | 20 | var stage string 21 | 22 | type CssToJsonRequest struct { 23 | Content string 24 | } 25 | 26 | func handler(ctx context.Context, s3Event events.S3Event) { 27 | 28 | sess := session.Must(session.NewSession()) 29 | lClient := lambda2.New(sess) 30 | 31 | for _, record := range s3Event.Records { 32 | 33 | log.Printf("%+v", record) 34 | 35 | content, _ := download(sess, &record) 36 | payload, _ := request(content) 37 | 38 | log.Printf("%s", payload) 39 | 40 | res, err := lClient.Invoke(&lambda2.InvokeInput{FunctionName: aws.String("goclassifieds-api-" + stage + "-CssToJson"), Payload: payload}) 41 | if err != nil { 42 | log.Printf("error invoking entity validation: %s", err.Error()) 43 | log.Printf("response: %s", res) 44 | } 45 | 46 | log.Printf("json: %s", res.Payload) 47 | write(sess, res.Payload, &record) 48 | 49 | } 50 | } 51 | 52 | func request(content []byte) ([]byte, error) { 53 | 54 | request := CssToJsonRequest{ 55 | Content: string(content), 56 | } 57 | 58 | payload, err := json.Marshal(request) 59 | if err != nil { 60 | log.Printf("Error marshalling css to json request: %s", err.Error()) 61 | return nil, errors.New("Error marshalling css to json request") 62 | } 63 | 64 | return payload, nil 65 | } 66 | 67 | func download(sess *session.Session, record *events.S3EventRecord) ([]byte, error) { 68 | 69 | buf := aws.NewWriteAtBuffer([]byte{}) 70 | 71 | downloader := s3manager.NewDownloader(sess) 72 | 73 | _, err := downloader.Download(buf, &s3.GetObjectInput{ 74 | Bucket: aws.String(record.S3.Bucket.Name), 75 | Key: aws.String(record.S3.Object.Key), 76 | }) 77 | 78 | if err != nil { 79 | return []byte(""), err 80 | } 81 | 82 | return buf.Bytes(), nil 83 | 84 | } 85 | 86 | func write(sess *session.Session, content []byte, record *events.S3EventRecord) error { 87 | 88 | uploader := s3manager.NewUploader(sess) 89 | 90 | _, err := uploader.Upload(&s3manager.UploadInput{ 91 | Bucket: aws.String(record.S3.Bucket.Name), 92 | Key: aws.String(record.S3.Object.Key + ".json"), 93 | Body: bytes.NewReader(content), 94 | ContentType: aws.String("application/json"), 95 | }) 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | } 101 | 102 | func main() { 103 | stage = os.Getenv("STAGE") 104 | lambda.Start(handler) 105 | } 106 | -------------------------------------------------------------------------------- /func/css_to_json/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | 3 | esbuild( 4 | name = "css_to_json", 5 | entry_point = "index.js", 6 | platform = "node", 7 | target = "node10", 8 | tool = select({ 9 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 10 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 11 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 12 | }), 13 | deps = [ 14 | "@npm//cssjson", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /func/css_to_json/index.js: -------------------------------------------------------------------------------- 1 | var cssjson = require("cssjson"); 2 | 3 | module.exports.handler = function(event, context, callback) { 4 | console.log('hello world'); 5 | console.log(event); 6 | console.log(context); 7 | var json = cssjson.toJSON(event.Content, { comments: true }); 8 | console.log(json); 9 | callback(null, json); 10 | } -------------------------------------------------------------------------------- /func/enforce_contract/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | # load("//:node_deps.bzl", "setup_node_repositories") 3 | 4 | # setup_node_repositories() 5 | 6 | esbuild( 7 | name = "enforce_contract", 8 | entry_point = "index.js", 9 | platform = "node", 10 | target = "node10", 11 | tool = select({ 12 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 13 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 14 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 15 | }), 16 | deps = [ 17 | "@npm//ajv", 18 | ], 19 | ) -------------------------------------------------------------------------------- /func/enforce_contract/index.js: -------------------------------------------------------------------------------- 1 | const Ajv = require("ajv") 2 | const ajv = new Ajv() // options can be passed, e.g. {allErrors: true} 3 | 4 | module.exports.handler = function(event, context, callback) { 5 | console.log('hello world'); 6 | console.log(event); 7 | console.log(context); 8 | 9 | /*const schema = { 10 | type: "object", 11 | properties: { 12 | foo: {type: "integer"}, 13 | bar: {type: "string"} 14 | }, 15 | required: ["foo"], 16 | additionalProperties: false 17 | }*/ 18 | 19 | const contract = event.Contract; 20 | const schema = contract.schema; 21 | if (typeof(schema['$schema']) !== 'undefined') { 22 | delete schema['$schema']; 23 | } 24 | 25 | console.log('schema', schema); 26 | 27 | const validate = ajv.compile(schema) 28 | 29 | /*const data = { 30 | foo: "blah", 31 | bar: "abc" 32 | }*/ 33 | 34 | const data = event.Entity; 35 | console.log('entity', data); 36 | 37 | const valid = validate(data) 38 | console.log('errors', validate.errors) 39 | 40 | const res = { 41 | Entity: { ...data, userId: event.UserId }, 42 | Valid: validate.errors == null, 43 | Unauthorized: false, 44 | Errors: validate.errors 45 | }; 46 | 47 | //var json = cssjson.toJSON(event.Content, { comments: true }); 48 | //console.log(json); 49 | 50 | callback(null, res); 51 | } -------------------------------------------------------------------------------- /func/grant_access/AmazonRootCA1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /func/grant_access/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "grant_access", 5 | embed = [":grant_access_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/grant_access", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "grant_access_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/func/grant_access", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/gov", 20 | "//lib/entity", 21 | "//lib/utils", 22 | "@com_github_gocql_gocql//:gocql", 23 | "@com_github_tangzero_inflector//:inflector", 24 | "@com_github_aws_aws_lambda_go//lambda", 25 | ], 26 | ) 27 | -------------------------------------------------------------------------------- /func/grant_access/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "goclassifieds/lib/entity" 7 | "goclassifieds/lib/gov" 8 | "goclassifieds/lib/utils" 9 | "log" 10 | "os" 11 | "text/template" 12 | 13 | "github.com/aws/aws-lambda-go/lambda" 14 | "github.com/gocql/gocql" 15 | "github.com/tangzero/inflector" 16 | ) 17 | 18 | type TemplateBindValueFunc func(value interface{}) string 19 | 20 | func handler(ctx context.Context, payload *gov.GrantAccessRequest) (gov.GrantAccessResponse, error) { 21 | 22 | utils.LogUsageForLambdaWithInput(payload.LogUsageLambdaInput) 23 | results := make([]map[string]interface{}, 0) 24 | keyspacesConnected := false 25 | 26 | cluster := gocql.NewCluster("cassandra.us-east-1.amazonaws.com") 27 | cluster.Keyspace = "ClassifiedsDev" 28 | cluster.Port = 9142 29 | cluster.Consistency = gocql.LocalOne // gocql.LocalQuorum 30 | cluster.Authenticator = &gocql.PasswordAuthenticator{Username: os.Getenv("KEYSPACE_USERNAME"), Password: os.Getenv("KEYSPACE_PASSWORD")} 31 | cluster.SslOpts = &gocql.SslOptions{Config: &tls.Config{ServerName: "cassandra.us-east-1.amazonaws.com"}, CaPath: "AmazonRootCA1.pem", EnableHostVerification: true} 32 | cluster.PoolConfig = gocql.PoolConfig{HostSelectionPolicy: /*gocql.TokenAwareHostPolicy(*/ gocql.DCAwareRoundRobinPolicy("us-east-1") /*)*/} 33 | cSession, err := cluster.CreateSession() 34 | if err != nil { 35 | // log.Fatal(err) 36 | log.Printf("connection to keyspaxces failed %s", err) 37 | } else { 38 | keyspacesConnected = true 39 | } 40 | 41 | log.Print("After keyspaces connect") 42 | 43 | if keyspacesConnected { 44 | 45 | resourceParams := &gov.ResourceManagerParams{ 46 | Session: cSession, 47 | Request: payload, 48 | // Resource: fmt.Sprint(payload.Resource), 49 | // Operation: fmt.Sprint(payload.Operation), 50 | } 51 | 52 | resourceManager, _ := ResourceManager(resourceParams) 53 | allAttributes := make([]entity.EntityAttribute, 0) 54 | data := &entity.EntityFinderDataBag{ 55 | Attributes: allAttributes, 56 | Metadata: map[string]interface{}{ 57 | "user": payload.User, 58 | "type": payload.Type, 59 | "resource": payload.Resource, 60 | "asset": payload.Asset, 61 | "op": payload.Operation, 62 | }, 63 | } 64 | 65 | results = resourceManager.Find("default", "grant_access", data) 66 | 67 | } 68 | 69 | /*b, _ := json.Marshal(results) 70 | log.Print(string(b))*/ 71 | 72 | grant := len(results) != 0 73 | 74 | if len(payload.AdditionalResources) != 0 { 75 | for _, r := range payload.AdditionalResources { 76 | if r.User == payload.User && r.Type == payload.Type && r.Resource == payload.Resource && r.Asset == payload.Asset && r.Operation == payload.Operation { 77 | grant = true 78 | break 79 | } 80 | } 81 | } 82 | 83 | return gov.GrantAccessResponse{ 84 | Grant: grant, 85 | }, nil 86 | 87 | } 88 | 89 | func ResourceManager(params *gov.ResourceManagerParams) (entity.Manager, error) { 90 | entityName := "resource" 91 | bindings := &entity.VariableBindings{Values: make([]interface{}, 0)} 92 | funcMap := template.FuncMap{ 93 | "bindValue": TemplateBindValue(bindings), 94 | } 95 | t, err := template.New("").Funcs(funcMap).Parse(gov.Query()) 96 | if err != nil { 97 | log.Printf("Error: %s", err.Error()) 98 | return entity.EntityManager{}, err 99 | } 100 | manager := entity.EntityManager{ 101 | Config: entity.EntityConfig{ 102 | SingularName: entityName, 103 | PluralName: inflector.Pluralize(entityName), 104 | IdKey: "id", 105 | Stage: os.Getenv("STAGE"), 106 | }, 107 | Creator: entity.DefaultCreatorAdaptor{}, 108 | Validators: map[string]entity.Validator{ 109 | "default": entity.DefaultValidatorAdaptor{}, 110 | }, 111 | Storages: map[string]entity.Storage{}, 112 | Finders: map[string]entity.Finder{ 113 | "default": entity.CqlTemplateFinder{ 114 | Config: entity.CqlTemplateFinderConfig{ 115 | Session: params.Session, 116 | Template: t, 117 | Table: inflector.Pluralize(entityName) + "2", 118 | Bindings: bindings, 119 | Aliases: map[string]string{}, 120 | }, 121 | }, 122 | }, 123 | Hooks: map[entity.Hooks]entity.EntityHook{}, 124 | CollectionHooks: map[string]entity.EntityCollectionHook{}, 125 | } 126 | 127 | return manager, nil 128 | } 129 | 130 | func TemplateBindValue(bindings *entity.VariableBindings) TemplateBindValueFunc { 131 | return func(value interface{}) string { 132 | bindings.Values = append(bindings.Values, value) 133 | return "?" 134 | } 135 | } 136 | 137 | func main() { 138 | log.SetFlags(0) 139 | // Make the handler available for Remote Procedure Call by AWS Lambda 140 | lambda.Start(handler) 141 | } 142 | -------------------------------------------------------------------------------- /func/hello_world/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "hello_world", 5 | embed = [":hello_world_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/hello_world", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "hello_world_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/hello_world", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "@com_github_aws_aws_lambda_go//events", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /func/hello_world/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aws/aws-lambda-go/events" 5 | "github.com/aws/aws-lambda-go/lambda" 6 | ) 7 | 8 | func HandleRequest(req *events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { 9 | body := "{\"works\": true}" 10 | return events.APIGatewayProxyResponse{StatusCode: 200, Body: body, Headers: map[string]string{"Content-Type": "application/json"}}, nil 11 | } 12 | 13 | func main() { 14 | lambda.Start(HandleRequest) 15 | } 16 | -------------------------------------------------------------------------------- /func/index_entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "index_entity", 5 | embed = [":index_entity_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/index_entity", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "index_entity_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/index_entity", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/ads", 19 | "//lib/attr", 20 | "//lib/cc", 21 | "//lib/entity", 22 | "//lib/vocab", 23 | "//lib/sign", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_lambda_go//lambda", 26 | "@com_github_aws_aws_sdk_go//aws/session", 27 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 28 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 29 | "@com_github_mitchellh_mapstructure//:mapstructure", 30 | "@com_github_tangzero_inflector//:inflector", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /func/index_entity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "goclassifieds/lib/ads" 6 | "goclassifieds/lib/attr" 7 | "goclassifieds/lib/cc" 8 | "goclassifieds/lib/entity" 9 | "goclassifieds/lib/sign" 10 | "goclassifieds/lib/vocab" 11 | "os" 12 | "strings" 13 | 14 | "github.com/aws/aws-lambda-go/events" 15 | "github.com/aws/aws-lambda-go/lambda" 16 | session "github.com/aws/aws-sdk-go/aws/session" 17 | elasticsearch7 "github.com/elastic/go-elasticsearch/v7" 18 | opensearch "github.com/opensearch-project/opensearch-go" 19 | 20 | "github.com/mitchellh/mapstructure" 21 | "github.com/tangzero/inflector" 22 | ) 23 | 24 | func handler(ctx context.Context, s3Event events.S3Event) { 25 | 26 | elasticCfg := elasticsearch7.Config{ 27 | Addresses: []string{os.Getenv("ELASTIC_URL")}, 28 | } 29 | 30 | esClient, err := elasticsearch7.NewClient(elasticCfg) 31 | if err != nil { 32 | 33 | } 34 | 35 | awsSigner := sign.AwsSigner{ 36 | Service: "es", 37 | Region: "us-east-1", 38 | } 39 | 40 | opensearchCfg := opensearch.Config{ 41 | Addresses: []string{os.Getenv("ELASTIC_URL")}, 42 | Signer: awsSigner, 43 | } 44 | 45 | osClient, err := opensearch.NewClient(opensearchCfg) 46 | if err != nil { 47 | 48 | } 49 | 50 | sess := session.Must(session.NewSession()) 51 | 52 | for _, record := range s3Event.Records { 53 | 54 | pieces := strings.Split(record.S3.Object.Key, "/") 55 | 56 | pluralName := inflector.Pluralize(pieces[0]) 57 | singularName := inflector.Singularize(pieces[0]) 58 | 59 | entityManager := entity.NewDefaultManager(entity.DefaultManagerConfig{ 60 | SingularName: singularName, 61 | PluralName: pluralName, 62 | Index: "classified_" + pluralName, 63 | EsClient: esClient, 64 | OsClient: osClient, 65 | Session: sess, 66 | UserId: "", 67 | Stage: os.Getenv("STAGE"), 68 | BucketName: os.Getenv("BUCKET_NAME"), 69 | }) 70 | 71 | id := pieces[1][0 : len(pieces[1])-8] 72 | ent := entityManager.Load(id, "default") 73 | 74 | if singularName == "ad" { 75 | ent = IndexAd(ent) 76 | } else if singularName == "panelpage" { 77 | ent = IndexPanelPage(ent) 78 | } 79 | 80 | entityManager.Save(ent, "elastic") 81 | } 82 | } 83 | 84 | func IndexAd(obj map[string]interface{}) map[string]interface{} { 85 | 86 | var item ads.Ad 87 | mapstructure.Decode(obj, &item) 88 | 89 | allAttrValues := make([]attr.AttributeValue, 0) 90 | for _, attrValue := range item.Attributes { 91 | attributesFlattened := attr.FlattenAttributeValue(attrValue) 92 | for _, flatAttr := range attributesFlattened { 93 | attr.FinalizeAttributeValue(&flatAttr) 94 | allAttrValues = append(allAttrValues, flatAttr) 95 | } 96 | } 97 | item.Attributes = allAttrValues 98 | 99 | for index, featureSet := range item.FeatureSets { 100 | allFeatureTerms := make([]vocab.Term, 0) 101 | for _, term := range featureSet.Terms { 102 | flatTerms := vocab.FlattenTerm(term, true) 103 | for _, flatTerm := range flatTerms { 104 | allFeatureTerms = append(allFeatureTerms, flatTerm) 105 | } 106 | } 107 | item.FeatureSets[index].Terms = allFeatureTerms 108 | } 109 | 110 | ent, _ := ads.ToEntity(&item) 111 | return ent 112 | 113 | } 114 | 115 | func IndexPanelPage(obj map[string]interface{}) map[string]interface{} { 116 | 117 | var item cc.PanelPage 118 | mapstructure.Decode(obj, &item) 119 | 120 | item.GridItems = make([]cc.GridItem, 0) 121 | item.Contexts = make([]cc.InlineContext, 0) 122 | item.Panels = make([]cc.Panel, 0) 123 | item.RowSettings = make([]cc.LayoutSetting, 0) 124 | 125 | ent, _ := cc.ToPanelPageEntity(&item) 126 | return ent 127 | 128 | } 129 | 130 | func main() { 131 | lambda.Start(handler) 132 | } 133 | -------------------------------------------------------------------------------- /func/readable_profiles/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "readable_profiles", 5 | embed = [":readable_profiles_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/readable_profiles", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "readable_profiles_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/readable_profiles", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/entity", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /func/readable_profiles/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "goclassifieds/lib/entity" 7 | "log" 8 | 9 | "github.com/aws/aws-lambda-go/lambda" 10 | ) 11 | 12 | func handler(ctx context.Context, payload *entity.EntityDataRequest) (entity.EntityDataResponse, error) { 13 | log.Print("Inside readable_profiles") 14 | log.Printf("Entity: %s", payload.EntityName) 15 | 16 | rootIds := make(map[string]bool) 17 | for _, p := range payload.Data { 18 | id := fmt.Sprint(p["id"]) 19 | parentId := fmt.Sprint(p["parentId"]) 20 | _, ok := rootIds[id] 21 | if !ok && (parentId == "" || parentId == "") { 22 | rootIds[id] = true 23 | } 24 | } 25 | for _, p := range payload.Data { 26 | parentId := fmt.Sprint(p["parentId"]) 27 | _, ok := rootIds[parentId] 28 | if !ok && parentId != "" && parentId != "" { 29 | rootIds[parentId] = true 30 | } 31 | } 32 | i := 0 33 | ids := make([]map[string]interface{}, len(rootIds)) 34 | for key := range rootIds { 35 | ids[i] = map[string]interface{}{"value": key} 36 | i++ 37 | } 38 | 39 | return entity.EntityDataResponse{ 40 | Data: ids, 41 | }, nil 42 | } 43 | 44 | func main() { 45 | // Make the handler available for Remote Procedure Call by AWS Lambda 46 | lambda.Start(handler) 47 | } 48 | -------------------------------------------------------------------------------- /func/renewable_record/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "renewable_record", 5 | embed = [":renewable_record_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/renewable_record", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "renewable_record_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/func/renewable_record", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/entity", 20 | "//lib/sign", 21 | "//lib/utils", 22 | "@com_github_aws_aws_lambda_go//lambda", 23 | "@com_github_aws_aws_lambda_go//events", 24 | "@com_github_aws_aws_sdk_go//aws/session", 25 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 26 | ], 27 | ) 28 | -------------------------------------------------------------------------------- /func/scss_to_css/BUILD.bazel: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollthecloudinc/hedge/5c8fca87dcdc72b61fd1faba2eab0d46d67d73c0/func/scss_to_css/BUILD.bazel -------------------------------------------------------------------------------- /func/scss_to_css/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollthecloudinc/hedge/5c8fca87dcdc72b61fd1faba2eab0d46d67d73c0/func/scss_to_css/index.js -------------------------------------------------------------------------------- /func/validate_entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "validate_entity", 5 | embed = [":validate_entity_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/validate_entity", 9 | visibility = ["//visibility:public"], 10 | out = "bootstrap" 11 | ) 12 | 13 | go_library( 14 | name = "validate_entity_lib", 15 | srcs = ["main.go"], 16 | importpath = "goclassifieds/func/validate_entity", 17 | visibility = ["//visibility:private"], 18 | deps = [ 19 | "//lib/ads", 20 | "//lib/cc", 21 | "//lib/chat", 22 | "//lib/entity", 23 | "//lib/profiles", 24 | "//lib/utils", 25 | "//lib/vocab", 26 | "@com_github_aws_aws_lambda_go//lambda", 27 | "@com_github_go_playground_validator_v10//:validator", 28 | ], 29 | ) 30 | -------------------------------------------------------------------------------- /func/watttime_demo/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "watttime_demo", 5 | embed = [":watttime_demo_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/func/watttime_demo", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "watttime_demo_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/func/watttime_demo", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/watttime", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /func/watttime_demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "os" 8 | 9 | "goclassifieds/lib/watttime" 10 | 11 | "github.com/aws/aws-lambda-go/lambda" 12 | ) 13 | 14 | type RegionIntensity struct { 15 | Name string 16 | Ba string 17 | Intensity int 18 | } 19 | 20 | func handler(ctx context.Context, b json.RawMessage) { 21 | log.Print("top handler") 22 | 23 | loginInput := &watttime.LoginInput{ 24 | Username: os.Getenv("WATTTIME_USERNAME"), 25 | Password: os.Getenv("WATTTIME_PASSWORD"), 26 | } 27 | 28 | loginRes, err := watttime.Login(loginInput) 29 | if err != nil { 30 | log.Print(err.Error()) 31 | } 32 | 33 | // log.Printf("token = %s", loginRes.Token) 34 | 35 | regionsInput := &watttime.GridRegionsInput{ 36 | Token: loginRes.Token, 37 | } 38 | 39 | gridRegionsRes, err := watttime.GridRegions(regionsInput) 40 | if err != nil { 41 | log.Print(err.Error()) 42 | } 43 | 44 | intensities := make([]*RegionIntensity, len(gridRegionsRes.GridRegions)) 45 | 46 | for index, region := range gridRegionsRes.GridRegions { 47 | log.Print("region: " + region.Name + " | Ba: " + region.Ba) 48 | indexInput := &watttime.IndexInput{ 49 | Token: loginRes.Token, 50 | Ba: region.Ba, 51 | } 52 | intensity := &RegionIntensity{ 53 | Name: region.Name, 54 | Ba: region.Ba, 55 | } 56 | intensities[index] = intensity 57 | indexRes, err := watttime.GetIndex(indexInput) 58 | if err != nil { 59 | log.Print(err.Error()) 60 | } else { 61 | intensity.Intensity = indexRes.Percent 62 | log.Printf("Grid Intensity: %d", indexRes.Percent) 63 | log.Printf("Grid MOER: %f", indexRes.Moer) 64 | } 65 | } 66 | } 67 | 68 | func main() { 69 | log.SetFlags(0) 70 | lambda.Start(handler) 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module goclassifieds 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/aws/aws-lambda-go v1.26.0 7 | github.com/aws/aws-sdk-go v1.42.27 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 9 | github.com/elastic/go-elasticsearch v0.0.0 10 | github.com/elastic/go-elasticsearch/v7 v7.14.0 11 | github.com/gin-gonic/gin v1.7.4 12 | github.com/go-playground/validator/v10 v10.9.0 13 | github.com/gocql/gocql v0.0.0-20210817081954-bc256bbb90de 14 | github.com/golang/protobuf v1.5.2 // indirect 15 | github.com/google/uuid v1.3.0 16 | github.com/lestrrat-go/jwx v1.2.6 17 | github.com/mitchellh/mapstructure v1.4.1 18 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 19 | github.com/modern-go/reflect2 v1.0.1 // indirect 20 | github.com/tangzero/inflector v1.0.0 21 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect 22 | gopkg.in/yaml.v2 v2.4.0 // indirect 23 | ) 24 | 25 | require ( 26 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect 27 | github.com/gin-contrib/sse v0.1.0 // indirect 28 | github.com/go-playground/locales v0.14.0 // indirect 29 | github.com/go-playground/universal-translator v0.18.0 // indirect 30 | github.com/goccy/go-json v0.7.6 // indirect 31 | github.com/golang/snappy v0.0.3 // indirect 32 | github.com/google/go-cmp v0.5.8 // indirect 33 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 34 | github.com/jmespath/go-jmespath v0.4.0 // indirect 35 | github.com/json-iterator/go v1.1.10 // indirect 36 | github.com/leodido/go-urn v1.2.1 // indirect 37 | github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect 38 | github.com/lestrrat-go/blackmagic v1.0.0 // indirect 39 | github.com/lestrrat-go/httpcc v1.0.0 // indirect 40 | github.com/lestrrat-go/iter v1.0.1 // indirect 41 | github.com/lestrrat-go/option v1.0.0 // indirect 42 | github.com/mattn/go-isatty v0.0.12 // indirect 43 | github.com/opensearch-project/opensearch-go v1.1.0 // indirect 44 | github.com/pkg/errors v0.9.1 // indirect 45 | github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00 46 | github.com/ugorji/go/codec v1.1.7 // indirect 47 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 48 | //golang.org/x/crypto/curve25519 v0.0.0-20210817164053-32db794688a5 // indirect 49 | golang.org/x/text v0.3.7 // indirect 50 | google.golang.org/protobuf v1.26.0 // indirect 51 | gopkg.in/inf.v0 v0.9.1 // indirect 52 | ) 53 | 54 | require ( 55 | github.com/shurcooL/graphql v0.0.0-20220606043923-3cf50f8a0a29 // indirect 56 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect 57 | golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb 58 | ) 59 | 60 | require ( 61 | github.com/MicahParks/keyfunc v1.5.1 // indirect 62 | github.com/golang-jwt/jwt/v4 v4.4.2 // indirect 63 | github.com/google/go-github/v46 v46.0.0 // indirect 64 | github.com/sethvargo/go-password v0.2.0 // indirect 65 | google.golang.org/appengine v1.6.7 // indirect 66 | ) 67 | -------------------------------------------------------------------------------- /iac/precheck-cognito/.gitignore: -------------------------------------------------------------------------------- 1 | Pulumi.local*.yaml -------------------------------------------------------------------------------- /iac/precheck-cognito/Pulumi.dev.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | precheck-cognito:SesVerifiedEmail: "VERIFIED email address to dispatch transactional emails" 3 | precheck-cognito:OidcDomain: "ykpt3qxd62.execute-api.us-east-1.amazonaws.com" 4 | precheck-cognito:OidcStage: "dev" 5 | precheck-cognito:LoginGovClientId: "urn:gov:gsa:openidconnect.profiles:sp:sso:dhs:precheck-dev-pkce" 6 | aws:region: "us-east-1" 7 | aws:profile: "precheck-dev" -------------------------------------------------------------------------------- /iac/precheck-cognito/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: precheck-cognito 2 | description: Authenication system using cognito and identity pools 3 | runtime: 4 | name: nodejs 5 | options: 6 | packagemanager: npm 7 | config: 8 | pulumi:tags: 9 | value: 10 | pulumi:template: aws-typescript 11 | -------------------------------------------------------------------------------- /iac/precheck-cognito/eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc.json" 4 | ], 5 | "ignorePatterns": [ 6 | "!**/*" 7 | ], 8 | "overrides": [ 9 | { 10 | "files": [ 11 | "*.ts", 12 | "*.tsx", 13 | "*.js", 14 | "*.jsx" 15 | ], 16 | "parserOptions": { 17 | "project": [ 18 | "apps/precheck-cognito/tsconfig.json" 19 | ] 20 | }, 21 | "rules": {} 22 | }, 23 | { 24 | "files": [ 25 | "*.ts", 26 | "*.tsx" 27 | ], 28 | "rules": {} 29 | }, 30 | { 31 | "files": [ 32 | "*.js", 33 | "*.jsx" 34 | ], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /iac/precheck-cognito/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { loadConfig, register } from 'tsconfig-paths' 4 | 5 | const config = loadConfig('.') 6 | if (config.resultType === 'failed') { 7 | console.log('Could not load tsconfig to map paths, aborting.') 8 | process.exit(1) 9 | } 10 | 11 | register({ 12 | baseUrl: config.absoluteBaseUrl, 13 | paths: config.paths 14 | }) 15 | 16 | import './pulumi' 17 | export * from './pulumi' 18 | -------------------------------------------------------------------------------- /iac/precheck-cognito/message/README.md: -------------------------------------------------------------------------------- 1 | npm install fails with node 20 best bet use 18. -------------------------------------------------------------------------------- /iac/precheck-cognito/message/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk'); 2 | const b64 = require('base64-js'); 3 | const encryptionSdk = require('@aws-crypto/client-node'); 4 | 5 | // Configure the encryption SDK client with the KMS key from the environment variables 6 | const { encrypt, decrypt } = encryptionSdk.buildClient(encryptionSdk.CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT); 7 | const generatorKeyId = process.env.KEY_ALIAS; // KMS Key Alias (e.g., alias/your-key) 8 | const keyIds = [process.env.KEY_ARN]; // KMS Key ARN (e.g., arn:aws:kms:region:account-id:key/key-id) 9 | const keyring = new encryptionSdk.KmsKeyringNode({ generatorKeyId, keyIds }); 10 | 11 | const sns = new AWS.SNS({ region: process.env.REGION }); // Default SNS region 12 | const ses = new AWS.SES({ region: process.env.REGION }); // Default SES region 13 | 14 | exports.handler = async (event) => { 15 | console.log('Received event:', JSON.stringify(event, null, 2)); 16 | 17 | const phoneNumber = event.request.userAttributes.phone_number; // E.164 format 18 | const encryptedCode = event.request.code; // Encrypted verification code 19 | const fallbackEmail = event.request.userAttributes.email; // Fallback email, if needed 20 | 21 | // Ensure required attributes are present 22 | if (!phoneNumber || !encryptedCode) { 23 | console.error('Missing required parameters: phone number or encrypted code'); 24 | throw new Error('Missing required parameters'); 25 | } 26 | 27 | let plainTextCode; 28 | try { 29 | // Decrypt the secret code using the encryption SDK 30 | console.log('Decrypting verification code...'); 31 | const { plaintext, messageHeader } = await decrypt(keyring, b64.toByteArray(encryptedCode)); 32 | plainTextCode = plaintext.toString('utf-8'); // Convert plaintext buffer to a string 33 | console.log('Decrypted verification code:', plainTextCode); 34 | } catch (decryptError) { 35 | console.error('Failed to decrypt verification code:', decryptError); 36 | throw new Error('Failed to decrypt verification code'); 37 | } 38 | 39 | try { 40 | // Step 1: Attempt to send SMS via SNS 41 | console.log('Attempting to send SMS via SNS...'); 42 | const smsResult = await sns 43 | .publish({ 44 | Message: `Your verification code is: ${plainTextCode}`, // The decrypted code 45 | PhoneNumber: phoneNumber, 46 | }) 47 | .promise(); 48 | 49 | console.log('SMS sent successfully:', smsResult); 50 | return event; // Return the unmodified event object to indicate success 51 | } catch (snsError) { 52 | console.error('Failed to send SMS via SNS:', snsError); 53 | 54 | // Step 2: Fallback to Email via SES 55 | if (fallbackEmail) { 56 | console.log('Falling back to email...'); 57 | try { 58 | const emailResult = await ses 59 | .sendEmail({ 60 | Source: process.env.SES_VERIFIED_EMAIL, // SES verified sender email 61 | Destination: { 62 | ToAddresses: [fallbackEmail], 63 | }, 64 | Message: { 65 | Subject: { 66 | Data: 'Your Verification Code', 67 | }, 68 | Body: { 69 | Text: { 70 | Data: `We couldn't send an SMS to your phone number. Your verification code is: ${plainTextCode}`, 71 | }, 72 | }, 73 | }, 74 | }) 75 | .promise(); 76 | console.log('Fallback email sent successfully:', emailResult); 77 | return event; // Return the unmodified event object to indicate success 78 | } catch (emailError) { 79 | console.error('Failed to send fallback email:', emailError); 80 | throw new Error('Failed to send SMS and fallback email'); 81 | } 82 | } 83 | 84 | throw new Error('Failed to send SMS and no fallback email is available'); 85 | } 86 | }; -------------------------------------------------------------------------------- /iac/precheck-cognito/message/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-cognito-message", 3 | "version": "1.0.0", 4 | "description": "A Lambda function to handle custom Cognito messages with SMS and email fallback.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"No test specified\" && exit 0" 9 | }, 10 | "author": "Telos", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@aws-crypto/client-node": "^4.2.0", 14 | "aws-sdk": "^2.1390.0", 15 | "base64-js": "^1.5.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /iac/precheck-cognito/post-confirmation/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | 3 | exports.handler = async (event) => { 4 | console.log("Event:", JSON.stringify(event, null, 2)); 5 | 6 | const userPoolId = event.userPoolId; 7 | const userName = event.userName; 8 | const email = event.request.userAttributes.email; 9 | const phoneNumberVerified = event.request.userAttributes.phone_number_verified; 10 | 11 | // Only proceed if phone number is verified 12 | if (phoneNumberVerified === "true" && email) { 13 | const cognito = new AWS.CognitoIdentityServiceProvider(); 14 | 15 | try { 16 | // Update the email_verified attribute to true 17 | await cognito.adminUpdateUserAttributes({ 18 | UserPoolId: userPoolId, 19 | Username: userName, 20 | UserAttributes: [ 21 | { 22 | Name: "email_verified", 23 | Value: "true", 24 | }, 25 | ], 26 | }).promise(); 27 | 28 | console.log(`Email verified for user: ${userName}`); 29 | } catch (error) { 30 | console.error("Error updating email_verified:", error); 31 | throw error; 32 | } 33 | } 34 | 35 | return event; 36 | }; -------------------------------------------------------------------------------- /iac/precheck-cognito/post-confirmation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-cognito-post-confirmation", 3 | "version": "1.0.0", 4 | "description": "A Lambda function to do things after user confirmation of account.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"No test specified\" && exit 0" 9 | }, 10 | "author": "Telos", 11 | "license": "MIT", 12 | "dependencies": { 13 | "aws-sdk": "^2.1390.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /iac/precheck-cognito/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-cognito", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/precheck-cognito", 6 | "targets": { 7 | "destroy": { 8 | "executor": "@telos-iac/nx-pulumi:destroy", 9 | "options": {} 10 | }, 11 | "up": { 12 | "executor": "@telos-iac/nx-pulumi:up", 13 | "options": {} 14 | }, 15 | "preview": { 16 | "executor": "@telos-iac/nx-pulumi:preview", 17 | "options": {} 18 | }, 19 | "refresh": { 20 | "executor": "@telos-iac/nx-pulumi:refresh", 21 | "options": {} 22 | } 23 | }, 24 | "tags": [] 25 | } 26 | -------------------------------------------------------------------------------- /iac/precheck-cognito/stack-reference.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from '@pulumi/pulumi'; 2 | 3 | type ExportTypes = typeof import('./pulumi'); 4 | type ExportTypesKey = keyof ExportTypes; 5 | type ExportTypesValue = ExportTypes[TKey]; 6 | 7 | type StrongTypedStackReference = Omit & { 8 | getOutput(name: pulumi.Input): ExportTypesValue; 9 | requireOutput(name: pulumi.Input): ExportTypesValue; 10 | }; 11 | 12 | export function getStackReference() { 13 | const org = pulumi.getOrganization(); 14 | const stack = pulumi.getStack(); 15 | return new pulumi.StackReference(`${org}/precheck-cognito/${stack}`) as StrongTypedStackReference; 16 | } 17 | -------------------------------------------------------------------------------- /iac/precheck-cognito/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json" 3 | } 4 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "../../.eslintrc.json" 4 | ], 5 | "ignorePatterns": [ 6 | "!**/*" 7 | ], 8 | "overrides": [ 9 | { 10 | "files": [ 11 | "*.ts", 12 | "*.tsx", 13 | "*.js", 14 | "*.jsx" 15 | ], 16 | "parserOptions": { 17 | "project": [ 18 | "apps/precheck-oidc-auth/tsconfig.json" 19 | ] 20 | }, 21 | "rules": {} 22 | }, 23 | { 24 | "files": [ 25 | "*.ts", 26 | "*.tsx" 27 | ], 28 | "rules": {} 29 | }, 30 | { 31 | "files": [ 32 | "*.js", 33 | "*.jsx" 34 | ], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/.gitignore: -------------------------------------------------------------------------------- 1 | Pulumi.local*.yaml -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/Pulumi.dev.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | precheck-oidc-auth:ApiDeploymentStage: dev 3 | precheck-oidc-auth:LoginGovRedirectUri: "https://5bh4unsxta.execute-api.us-east-2.amazonaws.com/dev/callback" 4 | aws:profile: precheck-dev 5 | aws:region: us-east-1 6 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: precheck-oidc-auth 2 | description: Centralized OIDC providing infinite integration capabilities unrestricted by vendor-to-vendor incompatibilities since this can be used as a middle man to satisfy gaps. 3 | runtime: 4 | name: nodejs 5 | options: 6 | packagemanager: npm 7 | config: 8 | pulumi:tags: 9 | value: 10 | pulumi:template: aws-typescript 11 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/README.md: -------------------------------------------------------------------------------- 1 | implementation of an OIDC adapter (pkce:prononciation of PKCE) to bridge compatibility gaps between Login.gov and AWS Cognito and centralize external auth and/or support internal legacy user databases without requiring explicit migrations. The following changes are included: 2 | 3 | **Pulumi Infrastructure:** 4 | 5 | Created DynamoDB tables for state, code verifier, and redirect URI tracking. 6 | Deployed API Gateway with Lambda-backed endpoints ( 7 | 8 | - /login 9 | - /callback 10 | - /token 11 | - /user 12 | 13 | Configured Lambda functions with required IAM permissions and environment variables. 14 | 15 | **Lambda Functions:** 16 | 17 | - loginLambda: Handles the /login endpoint to initiate the OIDC flow with PKCE (Proof Key for Code Exchange) and redirects users to Login.gov. 18 | - callbackLambda: Processes Login.gov responses at the /callback endpoint, exchanges authorization codes for tokens, and redirects users back to Cognito. 19 | - tokenLambda: Implements the /token endpoint for exchanging authorization codes for tokens. 20 | - userLambda: Retrieves user profile information from Cognito's /userinfo endpoint. 21 | 22 | **Features:** 23 | 24 | - Secure state management with short-lived TTL in DynamoDB for mitigating CSRF and replay attacks. 25 | - PKCE-based implementation for enhanced security in OIDC flows. 26 | - Dynamic discovery of Cognito endpoints using the OpenID Connect discovery document. 27 | - Comprehensive error handling and logging for debugging. 28 | 29 | **Testing Notes:** 30 | 31 | - Deployed and tested the full OIDC flow end-to-end, including Login.gov authentication and Cognito redirection. 32 | - Verified DynamoDB entries (state, authorization codes) and their TTL behavior. 33 | - Ensured error propagation for invalid or expired tokens. 34 | - Impact: This implementation enables seamless integration with Login.gov while maintaining compatibility with AWS Cognito, providing a robust and secure authentication solution for our monorepo. -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/callback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "callback", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1692.0", 14 | "jsonwebtoken": "^9.0.2", 15 | "jwks-rsa": "^3.1.0", 16 | "node-fetch": "^2.7.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { loadConfig, register } from 'tsconfig-paths' 4 | 5 | const config = loadConfig('.') 6 | if (config.resultType === 'failed') { 7 | console.log('Could not load tsconfig to map paths, aborting.') 8 | process.exit(1) 9 | } 10 | 11 | register({ 12 | baseUrl: config.absoluteBaseUrl, 13 | paths: config.paths 14 | }) 15 | 16 | import './pulumi' 17 | export * from './pulumi' 18 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/login/index.js: -------------------------------------------------------------------------------- 1 | const crypto = require("crypto"); 2 | const AWS = require("aws-sdk"); 3 | const logger = require("./logger"); 4 | 5 | const loginGovBaseUrl = process.env.LOGIN_GOV_BASE_URL; 6 | const loginGovClientId = process.env.LOGIN_GOV_CLIENT_ID; 7 | const loginGovRedirectUri = process.env.LOGIN_GOV_REDIRECT_URI; 8 | const stateTableName = process.env.STATE_TABLE_NAME; 9 | const redirectTableName = process.env.REDIRECT_TABLE_NAME; // New table for storing redirect URLs 10 | 11 | /** 12 | * Generates a random string for the PKCE code verifier. 13 | */ 14 | function generateCodeVerifier() { 15 | return crypto.randomBytes(32).toString("hex"); 16 | } 17 | 18 | function generateNonce() { 19 | return crypto.randomBytes(16).toString("hex"); // Generate a random 16-byte nonce 20 | } 21 | 22 | /** 23 | * Generates a code challenge from the code verifier using SHA256. 24 | */ 25 | function generateCodeChallenge(codeVerifier) { 26 | return crypto 27 | .createHash("sha256") 28 | .update(codeVerifier) 29 | .digest("base64") 30 | .replace(/\+/g, "-") 31 | .replace(/\//g, "_") 32 | .replace(/=/g, ""); 33 | } 34 | 35 | exports.handler = async (event) => { 36 | try { 37 | 38 | // Parse the query string to extract the original redirect URL 39 | const { queryStringParameters } = event; 40 | const originalRedirectUri = queryStringParameters?.redirect_uri; 41 | const state = queryStringParameters?.state; 42 | 43 | logger({ state }); 44 | 45 | if (!state || state == '') { 46 | console.error("Missing state in query string"); 47 | return { 48 | statusCode: 400, 49 | body: JSON.stringify({ error: "Missing state in query string" }), 50 | }; 51 | } 52 | 53 | if (!originalRedirectUri || originalRedirectUri == '') { 54 | console.error("Missing redirect_uri in query string"); 55 | return { 56 | statusCode: 400, 57 | body: JSON.stringify({ error: "Missing redirect_uri in query string" }), 58 | }; 59 | } 60 | 61 | 62 | // Generate a nonce 63 | const nonce = generateNonce(); 64 | 65 | // const state = crypto.randomBytes(16).toString("hex"); // Generate a random state 66 | const codeVerifier = generateCodeVerifier(); 67 | const codeChallenge = generateCodeChallenge(codeVerifier); 68 | 69 | // Store the state and code verifier in DynamoDB 70 | const dynamodb = new AWS.DynamoDB.DocumentClient(); 71 | 72 | await dynamodb 73 | .put({ 74 | TableName: stateTableName, 75 | Item: { 76 | state, 77 | codeVerifier, 78 | nonce, 79 | ttl: Math.floor(Date.now() / 1000) + 300, // 5-minute TTL 80 | }, 81 | }) 82 | .promise(); 83 | 84 | 85 | // Store the original redirect URL in the redirect table 86 | await dynamodb 87 | .put({ 88 | TableName: redirectTableName, 89 | Item: { 90 | state, 91 | redirectUri: originalRedirectUri, 92 | ttl: Math.floor(Date.now() / 1000) + 300, // 5-minute TTL 93 | }, 94 | }) 95 | .promise(); 96 | 97 | // Build the Login.gov authorization URL 98 | const redirectUrl = `${loginGovBaseUrl}?client_id=${loginGovClientId}&response_type=code&redirect_uri=${encodeURIComponent( 99 | loginGovRedirectUri 100 | )}&scope=openid+email+verified_at+x509_presented+x509_subject&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256&acr_values=${encodeURIComponent('urn:acr.login.gov:auth-only urn:gov:gsa:ac:classes:sp:PasswordProtectedTransport:duo')}&nonce=${nonce}`; 101 | 102 | // Return a redirect response 103 | return { 104 | statusCode: 302, 105 | headers: { 106 | Location: redirectUrl, 107 | }, 108 | }; 109 | } catch (error) { 110 | console.error("Error in Login Lambda:", error); 111 | return { 112 | statusCode: 500, 113 | body: JSON.stringify({ error: "Internal Server Error" }), 114 | }; 115 | } 116 | }; -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "login", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1692.0", 14 | "crypto": "^1.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "precheck-oidc-auth", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/precheck-oidc-auth", 6 | "targets": { 7 | "destroy": { 8 | "executor": "@telos-iac/nx-pulumi:destroy", 9 | "options": {} 10 | }, 11 | "up": { 12 | "executor": "@telos-iac/nx-pulumi:up", 13 | "options": {} 14 | }, 15 | "preview": { 16 | "executor": "@telos-iac/nx-pulumi:preview", 17 | "options": {} 18 | }, 19 | "refresh": { 20 | "executor": "@telos-iac/nx-pulumi:refresh", 21 | "options": {} 22 | } 23 | }, 24 | "tags": [] 25 | } 26 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/shared/logger.js: -------------------------------------------------------------------------------- 1 | // Helper function to format log messages with context variables 2 | const format = (level, contextVars, message, additionalData) => { 3 | const timestamp = new Date().toISOString(); 4 | const contextString = Object.entries(contextVars) 5 | .map(([key, value]) => `${key}=${value}`) 6 | .join(" "); 7 | const additionalString = additionalData ? JSON.stringify(additionalData) : ""; 8 | return `${timestamp} [${level}] ${contextString} ${message} ${additionalString}`; 9 | }; 10 | 11 | // Functional logger setup to override console methods 12 | const logger = (contextVars) => { 13 | const log = console.log; 14 | const error = console.error; 15 | const warn = console.warn; 16 | const debug = console.debug; 17 | 18 | // Wrap each console method with custom formatting 19 | console.log = (message, additionalData) => 20 | log(format("INFO", contextVars, message, additionalData)); 21 | 22 | console.error = (message, additionalData) => 23 | error(format("ERROR", contextVars, message, additionalData)); 24 | 25 | console.warn = (message, additionalData) => 26 | warn(format("WARN", contextVars, message, additionalData)); 27 | 28 | console.debug = (message, additionalData) => 29 | debug(format("DEBUG", contextVars, message, additionalData)); 30 | }; 31 | 32 | // Export the setup function 33 | module.exports = logger; -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/stack-reference.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from '@pulumi/pulumi'; 2 | 3 | type ExportTypes = typeof import('./pulumi'); 4 | type ExportTypesKey = keyof ExportTypes; 5 | type ExportTypesValue = ExportTypes[TKey]; 6 | 7 | type StrongTypedStackReference = Omit & { 8 | // eslint-disable-next-line no-unused-vars 9 | getOutput(name: pulumi.Input): ExportTypesValue; 10 | // eslint-disable-next-line no-unused-vars 11 | requireOutput(name: pulumi.Input): ExportTypesValue; 12 | }; 13 | 14 | export function getStackReference() { 15 | const org = pulumi.getOrganization(); 16 | const stack = pulumi.getStack(); 17 | return new pulumi.StackReference(`${org}/precheck-oidc-auth/${stack}`) as StrongTypedStackReference; 18 | } 19 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/token/index.js: -------------------------------------------------------------------------------- 1 | const AWS = require("aws-sdk"); 2 | const querystring = require("querystring"); // Used to parse form-encoded data 3 | const logger = require("./logger"); 4 | 5 | const dynamodb = new AWS.DynamoDB.DocumentClient(); 6 | 7 | exports.handler = async (event) => { 8 | try { 9 | // Parse the form-encoded body 10 | const body = querystring.parse(event.body); 11 | const { code /*, state , redirect_uri, client_id*/ } = body; 12 | 13 | logger({}); 14 | 15 | // Validate the input parameters 16 | if (!code) { 17 | console.error("Missing authorization code"); 18 | return { 19 | statusCode: 400, 20 | body: JSON.stringify({ error: "Missing authorization code" }), 21 | }; 22 | } 23 | 24 | // Retrieve the token data from DynamoDB using the authorization code 25 | const tokenData = await dynamodb 26 | .get({ 27 | TableName: process.env.AUTH_CODE_TABLE_NAME, 28 | Key: { auth_code: code }, 29 | }) 30 | .promise(); 31 | 32 | // Check if token data exists 33 | if (!tokenData.Item) { 34 | console.error("Invalid or expired authorization code"); 35 | return { 36 | statusCode: 400, 37 | body: JSON.stringify({ error: "Invalid or expired authorization code" }), 38 | }; 39 | } 40 | 41 | // No state provided in request for cognito but maybe others so leave it here 42 | /*if (tokenData.Item.state !== state) { 43 | console.error("State mismatch"); 44 | return { 45 | statusCode: 400, 46 | body: JSON.stringify({ error: "State mismatch" }), 47 | }; 48 | }*/ 49 | 50 | // Optional: Validate redirect_uri 51 | /*if (redirect_uri && tokenData.Item.redirect_uri !== redirect_uri) { 52 | console.error("Redirect URI mismatch"); 53 | return { 54 | statusCode: 400, 55 | body: JSON.stringify({ error: "Redirect URI mismatch" }), 56 | }; 57 | }*/ 58 | 59 | // Optional: Validate client_id 60 | /*if (client_id && process.env.COGNITO_APP_CLIENT_ID !== client_id) { 61 | console.error("Client ID mismatch"); 62 | return { 63 | statusCode: 400, 64 | body: JSON.stringify({ error: "Client ID mismatch" }), 65 | }; 66 | }*/ 67 | 68 | // Extract the tokens from the retrieved data 69 | const { id_token, access_token, refresh_token, expiration } = tokenData.Item; 70 | 71 | // Optional: Check if the tokens are expired 72 | const currentTimestamp = Math.floor(Date.now() / 1000); 73 | if (expiration < currentTimestamp) { 74 | console.error("Authorization code has expired"); 75 | return { 76 | statusCode: 400, 77 | body: JSON.stringify({ error: "Authorization code has expired" }), 78 | }; 79 | } 80 | 81 | // Return the tokens to the client 82 | return { 83 | statusCode: 200, 84 | body: JSON.stringify({ 85 | id_token, 86 | access_token, 87 | refresh_token, 88 | expires_in: expiration - currentTimestamp, // Remaining time until expiration 89 | token_type: 'Bearer' 90 | }), 91 | }; 92 | } catch (error) { 93 | console.error("Error retrieving token:", error); 94 | return { 95 | statusCode: 500, 96 | body: JSON.stringify({ error: "Internal server error", details: error.message }), 97 | }; 98 | } 99 | }; -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.1692.0", 14 | "querystring": "^0.2.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json" 3 | } 4 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/user/index.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | const logger = require("./logger"); 3 | 4 | exports.handler = async (event) => { 5 | logger({}); 6 | 7 | console.log("Received event:", JSON.stringify(event, null, 2)); 8 | 9 | // Extract the Authorization header 10 | const authorizationHeader = event.headers?.Authorization || event.headers?.authorization; 11 | if (!authorizationHeader || !authorizationHeader.startsWith("Bearer ")) { 12 | return { 13 | statusCode: 401, 14 | body: JSON.stringify({ error: "Unauthorized: Missing or invalid Authorization header" }), 15 | }; 16 | } 17 | 18 | const accessToken = authorizationHeader.split(" ")[1]; 19 | 20 | try { 21 | // Fetch user information from Login.gov `/userinfo` endpoint 22 | const userInfoResponse = await fetch(`${process.env.LOGIN_GOV_ISSUER}/api/openid_connect/userinfo`, { 23 | method: "GET", 24 | headers: { 25 | Authorization: `Bearer ${accessToken}`, 26 | }, 27 | }); 28 | 29 | if (!userInfoResponse.ok) { 30 | console.error("Failed to fetch user info from Login.gov:", userInfoResponse.statusText); 31 | return { 32 | statusCode: userInfoResponse.status, 33 | body: await userInfoResponse.text(), 34 | }; 35 | } 36 | 37 | const userInfo = await userInfoResponse.json(); 38 | 39 | // Return the user information 40 | return { 41 | statusCode: 200, 42 | body: JSON.stringify(userInfo), 43 | }; 44 | } catch (error) { 45 | console.error("Error fetching user info from Login.gov:", error); 46 | return { 47 | statusCode: 500, 48 | body: JSON.stringify({ error: "Failed to fetch user info", details: error.message }), 49 | }; 50 | } 51 | }; -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/user/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "user", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "node-fetch": "^2.7.0" 13 | } 14 | }, 15 | "node_modules/node-fetch": { 16 | "version": "2.7.0", 17 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 18 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 19 | "dependencies": { 20 | "whatwg-url": "^5.0.0" 21 | }, 22 | "engines": { 23 | "node": "4.x || >=6.0.0" 24 | }, 25 | "peerDependencies": { 26 | "encoding": "^0.1.0" 27 | }, 28 | "peerDependenciesMeta": { 29 | "encoding": { 30 | "optional": true 31 | } 32 | } 33 | }, 34 | "node_modules/tr46": { 35 | "version": "0.0.3", 36 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 37 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 38 | }, 39 | "node_modules/webidl-conversions": { 40 | "version": "3.0.1", 41 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 42 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 43 | }, 44 | "node_modules/whatwg-url": { 45 | "version": "5.0.0", 46 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 47 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 48 | "dependencies": { 49 | "tr46": "~0.0.3", 50 | "webidl-conversions": "^3.0.0" 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /iac/precheck-oidc-auth/user/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "node-fetch": "^2.7.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /job/renewable_report/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") 2 | 3 | go_binary( 4 | name = "renewable_report", 5 | embed = [":renewable_report_lib"], 6 | goarch = "amd64", 7 | goos = "linux", 8 | importpath = "goclassifieds/job/renewable_report", 9 | visibility = ["//visibility:public"], 10 | ) 11 | 12 | go_library( 13 | name = "renewable_report_lib", 14 | srcs = ["main.go"], 15 | importpath = "goclassifieds/job/renewable_report", 16 | visibility = ["//visibility:private"], 17 | deps = [ 18 | "//lib/entity", 19 | "//lib/sign", 20 | "//lib/utils", 21 | "//lib/repo", 22 | "//lib/watttime", 23 | "@com_github_aws_aws_lambda_go//lambda", 24 | "@com_github_aws_aws_lambda_go//events", 25 | "@com_github_aws_aws_sdk_go//aws/session", 26 | "@org_golang_x_oauth2//:go_default_library", 27 | "@com_github_shurcool_githubv4//:go_default_library", 28 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 29 | ], 30 | ) 31 | -------------------------------------------------------------------------------- /lib/ads/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "ads", 5 | srcs = ["ads.go"], 6 | importpath = "goclassifieds/lib/ads", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/attr", 10 | "//lib/vocab", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /lib/ads/ads.go: -------------------------------------------------------------------------------- 1 | package ads 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "goclassifieds/lib/attr" 7 | "goclassifieds/lib/vocab" 8 | "log" 9 | "time" 10 | ) 11 | 12 | type AdStatuses int32 13 | 14 | const ( 15 | Submitted AdStatuses = iota 16 | Approved 17 | Rejected 18 | Expired 19 | Deleted 20 | ) 21 | 22 | type AdListitemsRequest struct { 23 | TypeId string `form:"typeId" binding:"required"` 24 | SearchString string `form:"searchString"` 25 | Location string `form:"location"` 26 | Features []string `form:"features[]"` 27 | Page int `form:"page"` 28 | } 29 | 30 | type Ad struct { 31 | Id string `json:"id" validate:"required"` 32 | TypeId string `form:"typeId" json:"typeId" binding:"typeId" validate:"required"` 33 | Status *AdStatuses `form:"status" json:"status" validate:"required"` 34 | Title string `form:"title" json:"title" binding:"required" validate:"required"` 35 | Description string `form:"description" json:"description" binding:"required" validate:"required"` 36 | Location [2]float64 `form:"location[]" json:"location" binding:"required" validate:"required"` 37 | UserId string `form:"userId" json:"userId" validate:"required"` 38 | ProfileId string `form:"profileId" json:"profileId"` 39 | CityDisplay string `form:"cityDisplay" json:"cityDisplay" binding:"required" validate:"required"` 40 | Images []AdImage `form:"images[]" json:"images" validate:"dive"` 41 | Attributes []attr.AttributeValue `form:"attributes[]" json:"attributes" validate:"dive"` 42 | FeatureSets []vocab.Vocabulary `form:"featureSets[]" json:"featureSets" validate:"dive"` 43 | } 44 | 45 | type AdImage struct { 46 | Id string `form:"id" json:"id" binding:"required" validate:"required"` 47 | Path string `form:"path" json:"path" binding:"required" validate:"required"` 48 | Weight int `form:"weight" json:"weight" binding:"required" validate:"required"` 49 | } 50 | 51 | type AdLead struct { 52 | ProfileId string `form:"profileId" json:"profileId" binding:"required" validate:"required"` 53 | AdId string `form:"adId" json:"adId" binding:"required" validate:"required"` 54 | SenderId string `form:"senderId" json:"senderId"` 55 | Email string `form:"email" json:"email" binding:"required" validate:"required,email"` 56 | Phone string `form:"phone" json:"phone" binding:"required" validate:"required"` 57 | Message string `form:"message" json:"message" binding:"required" validate:"required"` 58 | CreatedAt time.Time `form:"createdAt" json:"createdAt" binding:"required" validate:"required"` 59 | } 60 | 61 | func ToEntity(ad *Ad) (map[string]interface{}, error) { 62 | var buf bytes.Buffer 63 | if err := json.NewEncoder(&buf).Encode(ad); err != nil { 64 | log.Fatalf("Error encoding query: %s", err) 65 | } 66 | jsonData, err := json.Marshal(ad) 67 | if err != nil { 68 | return nil, err 69 | } 70 | var entity map[string]interface{} 71 | err = json.Unmarshal(jsonData, &entity) 72 | return entity, nil 73 | } 74 | 75 | func ToLeadEntity(lead *AdLead) (map[string]interface{}, error) { 76 | jsonData, err := json.Marshal(lead) 77 | if err != nil { 78 | return nil, err 79 | } 80 | var entity map[string]interface{} 81 | err = json.Unmarshal(jsonData, &entity) 82 | return entity, nil 83 | } 84 | -------------------------------------------------------------------------------- /lib/attr/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "attr", 5 | srcs = ["attr.go"], 6 | importpath = "goclassifieds/lib/attr", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/attr/attr.go: -------------------------------------------------------------------------------- 1 | package attr 2 | 3 | import "strconv" 4 | 5 | type AttributeTypes int32 6 | 7 | const ( 8 | Number AttributeTypes = iota 9 | Text 10 | Complex 11 | Float 12 | Array 13 | Bool 14 | ) 15 | 16 | type AttributeValue struct { 17 | Name string `form:"name" json:"name" binding:"required"` 18 | DisplayName string `form:"displayName" json:"displayName" binding:"required"` 19 | Type AttributeTypes `form:"type" json:"type" binding:"required"` 20 | Value string `form:"value" json:"value" binding:"required"` 21 | IntValue int32 `json:"intValue"` 22 | FloatValue float64 `json:"floatValue"` 23 | ComputedValue string `form:"computedValue" json:"computedValue" binding:"required"` 24 | Attributes []AttributeValue `form:"attributes[]" json:"attributes"` 25 | } 26 | 27 | func FlattenAttributeValue(value AttributeValue) []AttributeValue { 28 | leafNodes := make([]AttributeValue, 0) 29 | if value.Attributes == nil || len(value.Attributes) == 0 { 30 | leafNodes = append(leafNodes, value) 31 | } else { 32 | for _, attr := range value.Attributes { 33 | flatChildren := FlattenAttributeValue(attr) 34 | for _, flatChild := range flatChildren { 35 | leafNodes = append(leafNodes, flatChild) 36 | } 37 | } 38 | } 39 | return leafNodes 40 | } 41 | 42 | func FinalizeAttributeValue(value *AttributeValue) { 43 | if value.Type == Number { 44 | computedValue, _ := strconv.ParseInt(value.ComputedValue, 10, 32) 45 | value.IntValue = int32(computedValue) 46 | } else if value.Type == Float { 47 | computedValue, _ := strconv.ParseFloat(value.ComputedValue, 64) 48 | value.FloatValue = computedValue 49 | } else { 50 | value.IntValue = 0 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/cc/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "cc", 5 | srcs = ["cc.go"], 6 | importpath = "goclassifieds/lib/cc", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/attr", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /lib/chat/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "chat", 5 | srcs = ["chat.go"], 6 | importpath = "goclassifieds/lib/chat", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/chat/chat.go: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | "time" 8 | ) 9 | 10 | type ChatConnection struct { 11 | ConnId string `form:"connId" json:"connId" binding:"required" validate:"required"` 12 | CreatedAt time.Time `form:"createdAt" json:"createdAt" binding:"required" validate:"required"` 13 | UserId string `form:"userId" json:"userId" binding:"required" validate:"required"` 14 | } 15 | 16 | type ChatConversation struct { 17 | UserId string `form:"userId" json:"userId" binding:"required" validate:"required"` 18 | RecipientId string `form:"recipientId" json:"recipientId" binding:"required" validate:"required"` 19 | RecipientLabel string `form:"recipientLabel" json:"recipientLabel" binding:"required" validate:"required"` 20 | } 21 | 22 | type ChatMessage struct { 23 | SenderId string `form:"senderId" json:"senderId" binding:"required" validate:"required"` 24 | RecipientId string `form:"recipientId" json:"recipientId" binding:"required" validate:"required"` 25 | Message string `form:"message" json:"message" binding:"required" validate:"required"` 26 | CreatedAt time.Time `form:"createdAt" json:"createdAt" binding:"required" validate:"required"` 27 | } 28 | 29 | func ToConnectionEntity(conn *ChatConnection) (map[string]interface{}, error) { 30 | var buf bytes.Buffer 31 | if err := json.NewEncoder(&buf).Encode(conn); err != nil { 32 | log.Fatalf("Error encoding query: %s", err) 33 | } 34 | jsonData, err := json.Marshal(conn) 35 | if err != nil { 36 | return nil, err 37 | } 38 | var entity map[string]interface{} 39 | err = json.Unmarshal(jsonData, &entity) 40 | return entity, nil 41 | } 42 | 43 | func ToConversationEntity(conv *ChatConversation) (map[string]interface{}, error) { 44 | var buf bytes.Buffer 45 | if err := json.NewEncoder(&buf).Encode(conv); err != nil { 46 | log.Fatalf("Error encoding query: %s", err) 47 | } 48 | jsonData, err := json.Marshal(conv) 49 | if err != nil { 50 | return nil, err 51 | } 52 | var entity map[string]interface{} 53 | err = json.Unmarshal(jsonData, &entity) 54 | return entity, nil 55 | } 56 | 57 | func ToMessageEntity(message *ChatMessage) (map[string]interface{}, error) { 58 | var buf bytes.Buffer 59 | if err := json.NewEncoder(&buf).Encode(message); err != nil { 60 | log.Fatalf("Error encoding query: %s", err) 61 | } 62 | jsonData, err := json.Marshal(message) 63 | if err != nil { 64 | return nil, err 65 | } 66 | var entity map[string]interface{} 67 | err = json.Unmarshal(jsonData, &entity) 68 | return entity, nil 69 | } 70 | -------------------------------------------------------------------------------- /lib/entity/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "entity", 5 | srcs = ["entity.go"], 6 | importpath = "goclassifieds/lib/entity", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/attr", 10 | "//lib/es", 11 | "//lib/os", 12 | "//lib/utils", 13 | "//lib/repo", 14 | "//lib/gov", 15 | "@com_github_aws_aws_lambda_go//events", 16 | "@com_github_aws_aws_sdk_go//aws", 17 | "@com_github_aws_aws_sdk_go//aws/session", 18 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 19 | "@com_github_aws_aws_sdk_go//service/lambda", 20 | "@com_github_aws_aws_sdk_go//service/s3", 21 | "@com_github_aws_aws_sdk_go//service/s3/s3manager", 22 | "@com_github_elastic_go_elasticsearch//esapi", 23 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 24 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 25 | "@com_github_opensearch_project_opensearch_go//opensearchapi", 26 | "@com_github_go_playground_validator_v10//:validator", 27 | "@com_github_gocql_gocql//:gocql", 28 | "@com_github_mitchellh_mapstructure//:mapstructure", 29 | "@com_github_tangzero_inflector//:inflector", 30 | "@com_github_shurcool_githubv4//:go_default_library", 31 | "@org_golang_x_oauth2//:go_default_library", 32 | "@com_github_google_go_github_v46//github", 33 | ], 34 | ) 35 | -------------------------------------------------------------------------------- /lib/es/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "es", 5 | srcs = ["es.go"], 6 | importpath = "goclassifieds/lib/es", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /lib/es/es.go: -------------------------------------------------------------------------------- 1 | package es 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "log" 8 | "strings" 9 | "text/template" 10 | 11 | elasticsearch7 "github.com/elastic/go-elasticsearch/v7" 12 | ) 13 | 14 | type SearchQueryBuilder interface { 15 | Build() map[string]interface{} 16 | GetIndex() string 17 | GetCollectionKey() string 18 | } 19 | 20 | type TemplateBuilder struct { 21 | Index string 22 | Data interface{} 23 | Template *template.Template 24 | Name string 25 | CollectionKey string 26 | } 27 | 28 | func (t TemplateBuilder) GetIndex() string { 29 | return t.Index 30 | } 31 | 32 | func (t TemplateBuilder) GetCollectionKey() string { 33 | return t.CollectionKey 34 | } 35 | 36 | func (t TemplateBuilder) Build() map[string]interface{} { 37 | var tb bytes.Buffer 38 | err := t.Template.ExecuteTemplate(&tb, t.Name, t.Data) 39 | if err != nil { 40 | log.Printf("Build Query Error: %s", err.Error()) 41 | } 42 | 43 | var query map[string]interface{} 44 | err = json.Unmarshal(tb.Bytes(), &query) 45 | if err != nil { 46 | log.Printf("Unmarshall Query Error: %s", err.Error()) 47 | } 48 | 49 | return query 50 | } 51 | 52 | func ExecuteQuery(esClient *elasticsearch7.Client, builder SearchQueryBuilder) []interface{} { 53 | query := builder.Build() 54 | 55 | var qb bytes.Buffer 56 | if err := json.NewEncoder(&qb).Encode(query); err != nil { 57 | log.Fatalf("Error encoding search query: %s", err) 58 | } 59 | log.Printf("Search Query: %s", qb.String()) 60 | 61 | return ExecuteSearch(esClient, &query, builder.GetIndex(), builder.GetCollectionKey()) 62 | } 63 | 64 | func ExecuteSearch(esClient *elasticsearch7.Client, query *map[string]interface{}, index string, collectionKey string) []interface{} { 65 | var buf bytes.Buffer 66 | if err := json.NewEncoder(&buf).Encode(query); err != nil { 67 | log.Fatalf("Error encoding query: %s", err) 68 | } 69 | res, err := esClient.Search( 70 | esClient.Search.WithContext(context.Background()), 71 | esClient.Search.WithIndex(index), 72 | esClient.Search.WithBody(&buf), 73 | // esClient.Search.WithTrackTotalHits(true), 74 | esClient.Search.WithPretty(), 75 | ) 76 | if err != nil { 77 | log.Fatalf("Error getting response: %s", err) 78 | } 79 | if res.IsError() { 80 | var e map[string]interface{} 81 | if err := json.NewDecoder(res.Body).Decode(&e); err != nil { 82 | log.Printf("Response: %s", res.Body) 83 | log.Fatalf("Error parsing the response body: %s", err) 84 | } else { 85 | // Print the response status and error information. 86 | log.Fatalf("[%s] %s: %s", 87 | res.Status(), 88 | e["error"].(map[string]interface{})["type"], 89 | e["error"].(map[string]interface{})["reason"], 90 | ) 91 | } 92 | } 93 | defer res.Body.Close() 94 | var r map[string]interface{} 95 | if err := json.NewDecoder(res.Body).Decode(&r); err != nil { 96 | log.Printf("Response: %s", res.Body) 97 | log.Fatalf("Error parsing the response body: %s", err) 98 | } 99 | pieces := strings.Split(collectionKey, ".") 100 | target := r[pieces[0]] 101 | for i, piece := range pieces { 102 | if i > 0 { 103 | target = target.(map[string]interface{})[piece] 104 | } 105 | } 106 | return target.([]interface{}) 107 | // return r["hits"].(map[string]interface{})["hits"].([]interface{}) 108 | /*var docs []interface{} 109 | for _, hit := range r["hits"].(map[string]interface{})["hits"].([]interface{}) { 110 | docs = append(docs, hit) 111 | } 112 | return docs*/ 113 | } 114 | -------------------------------------------------------------------------------- /lib/gov/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "gov", 5 | srcs = ["gov.go"], 6 | importpath = "goclassifieds/lib/gov", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/utils", 10 | "@com_github_google_go_github_v46//github", 11 | "@com_github_gocql_gocql//:gocql", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /lib/gov/gov.go: -------------------------------------------------------------------------------- 1 | package gov 2 | 3 | import ( 4 | "goclassifieds/lib/utils" 5 | "text/template" 6 | 7 | "github.com/gocql/gocql" 8 | ) 9 | 10 | const ( 11 | User ResourceUserTypes = iota 12 | Site 13 | ) 14 | 15 | var UserTypeMap = map[string]ResourceUserTypes{ 16 | "user": User, 17 | "site": Site, 18 | } 19 | 20 | type ResourceUserTypes int32 21 | 22 | const ( 23 | GithubRepo ResourceTypes = iota 24 | DruidSite 25 | CognitoUserPool 26 | CassandraTable 27 | ) 28 | 29 | var ResourceTypeMap = map[string]ResourceTypes{ 30 | "githubrepo": GithubRepo, 31 | "druidsite": DruidSite, 32 | "cognitouserpool": CognitoUserPool, 33 | "cassandratable": CassandraTable, 34 | } 35 | 36 | type ResourceTypes int32 37 | 38 | const ( 39 | Read ResourceOperations = iota 40 | Write 41 | Delete 42 | ) 43 | 44 | var OperationMap = map[string]ResourceOperations{ 45 | "read": Read, 46 | "write": Write, 47 | "delete": Delete, 48 | } 49 | 50 | type ResourceOperations int32 51 | 52 | type GrantAccessRequest struct { 53 | User string 54 | Type ResourceUserTypes 55 | Resource ResourceTypes 56 | Asset string 57 | Operation ResourceOperations 58 | AdditionalResources []Resource 59 | LogUsageLambdaInput *utils.LogUsageLambdaInput 60 | } 61 | 62 | type Resource struct { 63 | User string 64 | Type ResourceUserTypes 65 | Resource ResourceTypes 66 | Asset string 67 | Operation ResourceOperations 68 | } 69 | 70 | type GrantAccessResponse struct { 71 | Grant bool `json:"grant"` 72 | } 73 | 74 | type ResourceManagerParams struct { 75 | Session *gocql.Session 76 | Request *GrantAccessRequest 77 | Template *template.Template 78 | // Resource string 79 | // Operation string 80 | } 81 | 82 | func Query() string { 83 | return ` 84 | {{ define "grant_access" }} 85 | SELECT 86 | op 87 | FROM 88 | resources2 89 | WHERE 90 | user = {{ bindValue (index .Metadata "user" ) }} 91 | AND 92 | type = {{ bindValue (index .Metadata "type" ) }} 93 | AND 94 | resource = {{ bindValue (index .Metadata "resource" ) }} 95 | AND 96 | asset = {{ bindValue (index .Metadata "asset" ) }} 97 | AND 98 | op = {{ bindValue (index .Metadata "op" ) }} 99 | {{end}} 100 | ` 101 | } 102 | -------------------------------------------------------------------------------- /lib/hedge/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@npm//@bazel/esbuild:index.bzl", "esbuild") 2 | 3 | esbuild( 4 | name = "hedge_lib", 5 | entry_point = "index.js", 6 | platform = "node", 7 | target = "node10", 8 | tool = select({ 9 | "@bazel_tools//src/conditions:darwin": "@esbuild_darwin//:bin/esbuild", 10 | "@bazel_tools//src/conditions:windows": "@esbuild_windows//:esbuild.exe", 11 | "@bazel_tools//src/conditions:linux_x86_64": "@esbuild_linux//:bin/esbuild", 12 | }), 13 | deps = [ 14 | "@npm//matcher", 15 | ], 16 | ) 17 | 18 | load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm") 19 | 20 | pkg_npm( 21 | name = "hedge", 22 | srcs = ["package.json"], 23 | deps = [":hedge_lib"], 24 | substitutions = {"//internal/": "//"}, 25 | ) -------------------------------------------------------------------------------- /lib/hedge/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { isMatch } from 'matcher'; 4 | 5 | const objectsCache = new Map() 6 | const https = { 7 | request: (options, callback) => new HttpRequest({ options, callback }) 8 | }; 9 | 10 | class HttpRequest { 11 | _options 12 | _callback 13 | _handlers = new Map() 14 | constructor({ options, callback }) { 15 | this._options = options; 16 | this._callback = callback; 17 | } 18 | end = () => fetch("https://" + this._options.host + this._options.path) 19 | .then(res => ({ res, _: this._callback({ 20 | on: (evt, handler) => this._handlers[evt] = handler 21 | }) })) 22 | .then(({ res }) => res.text()) 23 | .then(data => this._handlers["data"](data)) 24 | .then(() => this._handlers['end']()) 25 | } 26 | 27 | export class Service { 28 | _document = {} 29 | 30 | constructor({ document }) { 31 | this._document = document 32 | } 33 | 34 | document = () => new Promise(res => res({ serviceDocument: this._document })) 35 | 36 | bounce = (path, options) => this.region({ path }) 37 | .then(({ region }) => region.document()) 38 | .then(({ regionDocument }) => fetch('https://' + regionDocument.origin + path, options)) 39 | 40 | region = ({ path }) => getObject({ path: 'renewable-report/report' }) 41 | .then(report => pickRegion({ path, report, service: this._document })) 42 | .then(({ region }) => ({ region: new Region({ document: this._document.regions.find(r => r.region === region) }) })) 43 | } 44 | 45 | export class Region { 46 | _document = {} 47 | constructor({ document }) { 48 | this._document = document; 49 | } 50 | document = () => new Promise(res => res({ regionDocument: this._document })) 51 | 52 | compare = ({ region }) => new Promise(res => res({ difference: 123 })) 53 | } 54 | 55 | export class Hedge { 56 | _service 57 | constructor({ service }) { 58 | this._service = service; 59 | } 60 | bounce = (path, options) => this._service.bounce(path, options) 61 | 62 | service = () => new Promise(res => res({ service: this._service })) 63 | 64 | region = ({ path }) => this._service.region({ path }) 65 | 66 | } 67 | 68 | export const hedge = ({ service }) => getObject({ path: 'services/' + service }).then(document => new Hedge({ service: new Service({ document }) })) 69 | 70 | function pickRegion({ service, report, path }) { 71 | const availableRegions = service.regions.filter(r => filterRegions({ region: r, uri: path })).map(r => r.region); 72 | const bestRegions = calculateBestRegions({ report }); 73 | let bestAvailableRegion = pickBestAvailableRegion({ availableRegions, bestRegions }); 74 | if (bestAvailableRegion === undefined) { 75 | bestAvailableRegion = report.defaultRegion !== undefined ? report.defaultRegion : 'eastus'; 76 | } 77 | const bestServiceRegion = service.regions.find(r => r.region === bestAvailableRegion); 78 | console.log('pickRegion', 'best service region', bestServiceRegion.region); 79 | console.log('pickRegion', 'picked region intensity', report.intensities[bestAvailableRegion]); 80 | return { origin: bestServiceRegion.origin, region: bestServiceRegion.region }; 81 | } 82 | 83 | async function getObject({ path, stage }) { 84 | let domain = 'store.hedge.earth'; 85 | let pathPrefix = '/'; 86 | /*if (stage === 'dev' || stage === undefined) { 87 | domain = "rollthecloudinc.github.io" 88 | pathPrefix = "/hedge-objects/" 89 | }*/ 90 | const options = { host: domain, path: pathPrefix + path + '.json' }; 91 | console.log('getObject', "options", options); 92 | return objectsCache.has(path) ? new Promise(res => res(objectsCache.get(path))) : new Promise(resolve => { 93 | https.request(options, res => { 94 | var str = ''; 95 | res.on('data', chunk => str += chunk); 96 | res.on('end', () => { 97 | console.log('getObject', "path", str); 98 | const obj = JSON.parse(str); 99 | objectsCache.set(path, obj); 100 | resolve(obj); 101 | }); 102 | }).end(); 103 | }); 104 | } 105 | 106 | function calculateBestRegions({ report }) { 107 | const intensitiesKeys = Object.keys(report.intensities); 108 | const bestRegions = []; 109 | intensitiesKeys.forEach(region => { 110 | if (report.intensities[region] > 0) { 111 | bestRegions.push({ intensity: report.intensities[region], region }); 112 | } else { 113 | console.log('pickRegion', 'toss', region); 114 | } 115 | }); 116 | bestRegions.sort((a, b) => a.intensity > b.intensity ? 1 : -1); 117 | console.log('calculateBestRegions', 'best regions', bestRegions); 118 | return bestRegions; 119 | } 120 | 121 | function pickBestAvailableRegion({ bestRegions, availableRegions }) { 122 | let bestAvailableRegion 123 | const len = bestRegions.length; 124 | for (let i = 0; i < len; i++) { 125 | const matchedAvailabilityRegion = availableRegions.find(region => region === bestRegions[i].region); 126 | if (matchedAvailabilityRegion !== undefined) { 127 | bestAvailableRegion = matchedAvailabilityRegion; 128 | break; 129 | } 130 | } 131 | console.log('pickBestAvailableRegion', 'best region', bestAvailableRegion); 132 | return bestAvailableRegion; 133 | } 134 | 135 | function provideFeedback({ bestRegions, bestRegion, report }) { 136 | console.log("provideFeedback") 137 | if (bestRegions.length > 0 && bestRegions[0].region === bestRegion.region) { 138 | console.log('Using region [' + bestRegion.region + '] with absolute lowest carbon intentity.'); 139 | } else if (bestRegions.length > 0 && bestRegions.find(r => r.region === bestRegion.region) !== undefined) { 140 | console.log('Using less optimal region [' + bestRegion.region + ']. If you were using [' + bestRegions[0].region + '] could have saved an extra ' + (report.intensities[bestRegion.region] - report.intensities[bestRegions[0].region]) + ' of carbon.'); 141 | } else { 142 | cobnsole.log('Using region [' + bestRegion.region + '] no intensity grid data available.'); 143 | } 144 | // @todo: default region difference. - carbon savings. 145 | } 146 | 147 | function filterRegions({ region, uri }) { 148 | return typeof(uri) === 'undefined' || typeof(region.paths) === 'undefined' || region.paths.length === 0 || region.paths.filter(path => isMatch(uri ,path)).length > 0 149 | } -------------------------------------------------------------------------------- /lib/hedge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rollthecloudinc/hedge", 3 | "version": "0.0.23", 4 | "main": "hedge_lib.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/rollthecloudinc/hedge" 8 | } 9 | } -------------------------------------------------------------------------------- /lib/os/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "os", 5 | srcs = ["os.go"], 6 | importpath = "goclassifieds/lib/os", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /lib/os/os.go: -------------------------------------------------------------------------------- 1 | package os 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "log" 8 | "strings" 9 | "text/template" 10 | 11 | opensearch "github.com/opensearch-project/opensearch-go" 12 | ) 13 | 14 | type SearchQueryBuilder interface { 15 | Build() map[string]interface{} 16 | GetIndex() string 17 | GetCollectionKey() string 18 | } 19 | 20 | type TemplateBuilder struct { 21 | Index string 22 | Data interface{} 23 | Template *template.Template 24 | Name string 25 | CollectionKey string 26 | } 27 | 28 | func (t TemplateBuilder) GetIndex() string { 29 | return t.Index 30 | } 31 | 32 | func (t TemplateBuilder) GetCollectionKey() string { 33 | return t.CollectionKey 34 | } 35 | 36 | func (t TemplateBuilder) Build() map[string]interface{} { 37 | var tb bytes.Buffer 38 | err := t.Template.ExecuteTemplate(&tb, t.Name, t.Data) 39 | if err != nil { 40 | log.Printf("Build Query Error: %s", err.Error()) 41 | } 42 | 43 | var query map[string]interface{} 44 | err = json.Unmarshal(tb.Bytes(), &query) 45 | if err != nil { 46 | log.Printf("Unmarshall Query Error: %s", err.Error()) 47 | } 48 | 49 | return query 50 | } 51 | 52 | func ExecuteQuery(esClient *opensearch.Client, builder SearchQueryBuilder) []interface{} { 53 | query := builder.Build() 54 | 55 | var qb bytes.Buffer 56 | if err := json.NewEncoder(&qb).Encode(query); err != nil { 57 | log.Fatalf("Error encoding search query: %s", err) 58 | } 59 | log.Printf("Search Query: %s", qb.String()) 60 | 61 | return ExecuteSearch(esClient, &query, builder.GetIndex(), builder.GetCollectionKey()) 62 | } 63 | 64 | func ExecuteSearch(esClient *opensearch.Client, query *map[string]interface{}, index string, collectionKey string) []interface{} { 65 | var buf bytes.Buffer 66 | if err := json.NewEncoder(&buf).Encode(query); err != nil { 67 | log.Fatalf("Error encoding query: %s", err) 68 | } 69 | res, err := esClient.Search( 70 | esClient.Search.WithContext(context.Background()), 71 | esClient.Search.WithIndex(index), 72 | esClient.Search.WithBody(&buf), 73 | // esClient.Search.WithTrackTotalHits(true), 74 | esClient.Search.WithPretty(), 75 | ) 76 | if err != nil { 77 | log.Fatalf("Error getting response: %s", err) 78 | } 79 | if res.IsError() { 80 | var e map[string]interface{} 81 | if err := json.NewDecoder(res.Body).Decode(&e); err != nil { 82 | buf := new(bytes.Buffer) 83 | buf.ReadFrom(res.Body) 84 | newStr := buf.String() 85 | log.Printf("Response: %s", newStr) 86 | log.Fatalf("Error parsing the response body: %s", err) 87 | } else { 88 | // Print the response status and error information. 89 | buf := new(bytes.Buffer) 90 | buf.ReadFrom(res.Body) 91 | msg := buf.String() 92 | log.Printf("open search request failure statuc: %s", res.Status()) 93 | log.Printf("open search request failure message: %s", msg) 94 | log.Fatalf("[%s] %s: %s", 95 | res.Status(), 96 | e["error"].(map[string]interface{})["type"], 97 | e["error"].(map[string]interface{})["reason"], 98 | ) 99 | } 100 | } 101 | defer res.Body.Close() 102 | var r map[string]interface{} 103 | if err := json.NewDecoder(res.Body).Decode(&r); err != nil { 104 | buf := new(bytes.Buffer) 105 | buf.ReadFrom(res.Body) 106 | newStr := buf.String() 107 | log.Printf("Response: %s", newStr) 108 | log.Fatalf("Error parsing the response body: %s", err) 109 | } 110 | pieces := strings.Split(collectionKey, ".") 111 | target := r[pieces[0]] 112 | for i, piece := range pieces { 113 | if i > 0 { 114 | target = target.(map[string]interface{})[piece] 115 | } 116 | } 117 | return target.([]interface{}) 118 | // return r["hits"].(map[string]interface{})["hits"].([]interface{}) 119 | /*var docs []interface{} 120 | for _, hit := range r["hits"].(map[string]interface{})["hits"].([]interface{}) { 121 | docs = append(docs, hit) 122 | } 123 | return docs*/ 124 | } 125 | -------------------------------------------------------------------------------- /lib/profiles/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "profiles", 5 | srcs = ["profiles.go"], 6 | importpath = "goclassifieds/lib/profiles", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/profiles/profiles.go: -------------------------------------------------------------------------------- 1 | package profiles 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | ) 8 | 9 | type ProfileStatuses int32 10 | 11 | const ( 12 | Submitted ProfileStatuses = iota 13 | Approved 14 | Rejected 15 | Deleted 16 | ) 17 | 18 | type ProfileTypes int32 19 | 20 | const ( 21 | Person ProfileTypes = iota 22 | Company 23 | Shop 24 | ) 25 | 26 | type ProfileSubtypes int32 27 | 28 | const ( 29 | Agent = iota 30 | Broker 31 | Dealer 32 | Seller 33 | ) 34 | 35 | type AdTypes int32 36 | 37 | const ( 38 | General AdTypes = iota 39 | RealEstate 40 | Rental 41 | Auto 42 | Job 43 | ) 44 | 45 | type PhoneNumberTypes int32 46 | 47 | const ( 48 | Email PhoneNumberTypes = iota 49 | Fax 50 | ) 51 | 52 | type LocationTypes int32 53 | 54 | const ( 55 | Home LocationTypes = iota 56 | Office 57 | ) 58 | 59 | type Profile struct { 60 | Id string `json:"id"` 61 | ParentId string `form:"parentId" json:"parentId"` 62 | UserId string `json:"userId" validate:"required"` 63 | Title string `form:"title" json:"title" validate:"required"` 64 | Status *ProfileStatuses `form:"status" json:"status" validate:"required"` 65 | Type *ProfileTypes `form:"type" json:"type" validate:"required"` 66 | Subtype *ProfileSubtypes `form:"subtype" json:"subtype" validate:"required"` 67 | Adspace *AdTypes `form:"adspace" json:"adspace" validate:"required"` 68 | FirstName string `form:"firstName" json:"firstName"` 69 | LastName string `form:"lastName" json:"lastName"` 70 | MiddleName string `form:"middleName" json:"middleName"` 71 | PreferredName string `form:"preferredName" json:"preferredName"` 72 | CompanyName string `form:"companyName" json:"companyName"` 73 | Email string `form:"email" json:"email"` 74 | Introduction string `form:"introduction" json:"introduction"` 75 | Logo *ProfileImage `form:"logo,omitempty" json:"logo,omitempty" binding:"omitempty" validate:"omitempty"` 76 | Headshot *ProfileImage `form:"headshot,omitempty" json:"headshot,omitempty" binding:"omitempty" validate:"omitempty"` 77 | PhoneNumbers []PhoneNumber `form:"phoneNumbers[]" json:"phoneNumbers" validate:"dive"` 78 | Locations []Location `form:"locations[]" json:"locations" validate:"dive"` 79 | EntityPermissions ProfilePermissions `json:"entityPermissions" validate:"required"` 80 | } 81 | 82 | type ProfileImage struct { 83 | Id string `form:"id" json:"id" binding:"required" validate:"required"` 84 | Path string `form:"path" json:"path" binding:"required" validate:"required"` 85 | Weight int `form:"weight" json:"weight" binding:"required" validate:"required"` 86 | } 87 | 88 | type PhoneNumber struct { 89 | Type *PhoneNumberTypes `form:"type" json:"type" binding:"required" validate:"required"` 90 | Value string `form:"value" json:"value" binding:"required" validate:"required"` 91 | } 92 | 93 | type Location struct { 94 | Title string `form:"title" json:"title" binding:"required" validate:"required"` 95 | Type *LocationTypes `form:"type" json:"type" binding:"required" validate:"required"` 96 | Address Address `form:"address" json:"address" binding:"required" validate:"required"` 97 | PhoneNumbers []PhoneNumber `form:"phoneNumbers[]" json:"phoneNumbers" validate:"required"` 98 | } 99 | 100 | type Address struct { 101 | Street1 string `form:"street1" json:"street1" binding:"required" validate:"required"` 102 | Street2 string `form:"street2" json:"street2"` 103 | Street3 string `form:"street3" json:"street3"` 104 | City string `form:"city" json:"city" binding:"required" validate:"required"` 105 | State string `form:"state" json:"state" binding:"required" validate:"required"` 106 | Zip string `form:"zip" json:"zip" binding:"required" validate:"required"` 107 | Country string `form:"country" json:"country" binding:"required" validate:"required"` 108 | } 109 | 110 | type ProfileNavItem struct { 111 | Id string `json:"id" binding:"required"` 112 | ParentId string `json:"parentId"` 113 | Title string `json:"title" binding:"required"` 114 | } 115 | 116 | type ProfilePermissions struct { 117 | ReadUserIds []string `json:"readUserIds"` 118 | WriteUserIds []string `json:"writeUserIds"` 119 | DeleteUserIds []string `json:"deleteUserIds"` 120 | } 121 | 122 | type ProfileListItemsQuery struct { 123 | ParentId string 124 | UserId string 125 | } 126 | 127 | type ProfileNavItemsQuery1 struct { 128 | UserId string 129 | } 130 | 131 | type ProfileNavItemsQuery2 struct { 132 | Ids []string 133 | Last int 134 | } 135 | 136 | func ToEntity(profile *Profile) (map[string]interface{}, error) { 137 | var buf bytes.Buffer 138 | if err := json.NewEncoder(&buf).Encode(profile); err != nil { 139 | log.Fatalf("Error encoding query: %s", err) 140 | } 141 | jsonData, err := json.Marshal(profile) 142 | if err != nil { 143 | return nil, err 144 | } 145 | var entity map[string]interface{} 146 | err = json.Unmarshal(jsonData, &entity) 147 | return entity, nil 148 | } 149 | -------------------------------------------------------------------------------- /lib/repo/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "repo", 5 | srcs = ["repo.go"], 6 | importpath = "goclassifieds/lib/repo", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "//lib/utils", 10 | "@com_github_shurcool_githubv4//:go_default_library", 11 | "@com_github_google_go_github_v46//github", 12 | "@org_golang_x_oauth2//:go_default_library", 13 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 14 | ], 15 | ) -------------------------------------------------------------------------------- /lib/shapeshift/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | #go_test( 3 | # name = "mainTest", 4 | # srcs = ["main_test.go"], 5 | #) 6 | 7 | go_library( 8 | name = "shapeshift", 9 | srcs = ["shapeshift.go"], 10 | importpath = "goclassifieds/lib/shapeshift", 11 | visibility = ["//visibility:public"], 12 | deps = [ 13 | "//lib/entity", 14 | "//lib/sign", 15 | "//lib/gov", 16 | "//lib/repo", 17 | "//lib/utils", 18 | "@com_github_aws_aws_lambda_go//events", 19 | "@com_github_aws_aws_lambda_go//lambda", 20 | "@com_github_aws_aws_sdk_go//aws/session", 21 | "@com_github_aws_aws_sdk_go//service/lambda", 22 | "@com_github_aws_aws_sdk_go//aws/credentials", 23 | "@com_github_aws_aws_sdk_go//aws/signer/v4:go_default_library", 24 | "@com_github_elastic_go_elasticsearch_v7//:go-elasticsearch", 25 | "@com_github_opensearch_project_opensearch_go//:opensearch-go", 26 | "@com_github_mitchellh_mapstructure//:mapstructure", 27 | "@com_github_tangzero_inflector//:inflector", 28 | "@com_github_shurcool_githubv4//:go_default_library", 29 | "@org_golang_x_oauth2//:go_default_library", 30 | "@com_github_aws_aws_sdk_go//aws", 31 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 32 | "@com_github_google_go_github_v46//github", 33 | "@com_github_golang_jwt_jwt_v4//:go_default_library", 34 | "@com_github_gocql_gocql//:gocql", 35 | ], 36 | ) 37 | -------------------------------------------------------------------------------- /lib/sign/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "sign", 5 | srcs = ["sign.go"], 6 | importpath = "goclassifieds/lib/sign", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | "@com_github_aws_aws_sdk_go//aws/credentials", 10 | "@com_github_aws_aws_sdk_go//aws/signer/v4:go_default_library", 11 | "@com_github_aws_aws_sdk_go//aws/session", 12 | "@com_github_aws_aws_sdk_go//service/cognitoidentity", 13 | "@com_github_aws_aws_sdk_go//service/cognitoidentityprovider", 14 | "@com_github_aws_aws_sdk_go//aws", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /lib/sign/sign.go: -------------------------------------------------------------------------------- 1 | package sign 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/aws/aws-sdk-go/aws" 11 | "github.com/aws/aws-sdk-go/aws/credentials" 12 | "github.com/aws/aws-sdk-go/aws/session" 13 | v4 "github.com/aws/aws-sdk-go/aws/signer/v4" 14 | "github.com/aws/aws-sdk-go/service/cognitoidentity" 15 | "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" 16 | ) 17 | 18 | type AwsSigner struct { 19 | Service string 20 | Region string 21 | Session *session.Session 22 | IdentityPoolId string 23 | Issuer string 24 | Token string 25 | IgnoreFailure bool 26 | } 27 | 28 | type UserPasswordAwsSigner struct { 29 | Service string 30 | Region string 31 | Session *session.Session 32 | IdentityPoolId string 33 | Issuer string 34 | IgnoreFailure bool 35 | Username string 36 | Password string 37 | CognitoAppClientId string 38 | } 39 | 40 | func (s AwsSigner) SignRequest(req *http.Request) error { 41 | 42 | svc := cognitoidentity.New(s.Session, aws.NewConfig().WithRegion("us-east-1")) 43 | 44 | idRes, err := svc.GetId(&cognitoidentity.GetIdInput{ 45 | IdentityPoolId: aws.String(s.IdentityPoolId), 46 | Logins: map[string]*string{ 47 | s.Issuer: aws.String(s.Token), 48 | }, 49 | }) 50 | if err != nil { 51 | log.Print("SignRequest GetId() error", err.Error()) 52 | // @todo: For now since indexing isn't required at the moment. 53 | return nil 54 | } 55 | 56 | credRes, err := svc.GetCredentialsForIdentity(&cognitoidentity.GetCredentialsForIdentityInput{ 57 | IdentityId: idRes.IdentityId, 58 | Logins: map[string]*string{ 59 | s.Issuer: aws.String(s.Token), 60 | }, 61 | }) 62 | if err != nil { 63 | log.Print("SignRequest GetId() error", err.Error()) 64 | // @todo: For now since indexing isn't required at the moment. 65 | return nil 66 | } 67 | 68 | // credentials := credentials.NewEnvCredentials() 69 | 70 | credentials := credentials.NewStaticCredentials( 71 | *credRes.Credentials.AccessKeyId, 72 | *credRes.Credentials.SecretKey, 73 | *credRes.Credentials.SessionToken, 74 | ) 75 | 76 | signer := v4.NewSigner(credentials) 77 | var b []byte 78 | if req.Body == nil { 79 | b = make([]byte, 0) 80 | } else { 81 | b2, err := ioutil.ReadAll(req.Body) 82 | if err != nil { 83 | return err 84 | } else { 85 | b = b2 86 | } 87 | } 88 | body := bytes.NewReader(b) 89 | /*hash := sha256.New() 90 | var hb []byte 91 | hash.Write(hb) 92 | req.Header.Add("X-Amz-Content-Sha256", string(hb))*/ 93 | _, err = signer.Sign(req, body, s.Service, s.Region, time.Now()) 94 | if err != nil { 95 | return err 96 | } 97 | return nil 98 | } 99 | 100 | func (s UserPasswordAwsSigner) SignRequest(req *http.Request) error { 101 | 102 | svc := cognitoidentity.New(s.Session, aws.NewConfig().WithRegion("us-east-1")) 103 | 104 | authParams := &cognitoidentityprovider.InitiateAuthInput{ 105 | AuthFlow: aws.String("USER_PASSWORD_AUTH"), 106 | AuthParameters: map[string]*string{ 107 | "USERNAME": aws.String(s.Username), 108 | "PASSWORD": aws.String(s.Password), 109 | }, 110 | ClientId: aws.String(s.CognitoAppClientId), // this is the app client ID 111 | } 112 | cip := cognitoidentityprovider.New(s.Session, aws.NewConfig().WithRegion("us-east-1")) 113 | authResp, err := cip.InitiateAuth(authParams) 114 | if err != nil { 115 | log.Print("InitiateAuth() error", err.Error()) 116 | // @todo: For now since indexing isn't required at the moment. 117 | return nil 118 | } 119 | 120 | idRes, err := svc.GetId(&cognitoidentity.GetIdInput{ 121 | IdentityPoolId: aws.String(s.IdentityPoolId), 122 | Logins: map[string]*string{ 123 | s.Issuer: authResp.AuthenticationResult.IdToken, 124 | }, 125 | }) 126 | if err != nil { 127 | log.Print("SignRequest GetId() error", err.Error()) 128 | // @todo: For now since indexing isn't required at the moment. 129 | return nil 130 | } 131 | 132 | credRes, err := svc.GetCredentialsForIdentity(&cognitoidentity.GetCredentialsForIdentityInput{ 133 | IdentityId: idRes.IdentityId, 134 | Logins: map[string]*string{ 135 | s.Issuer: authResp.AuthenticationResult.IdToken, 136 | }, 137 | }) 138 | if err != nil { 139 | log.Print("SignRequest GetId() error", err.Error()) 140 | // @todo: For now since indexing isn't required at the moment. 141 | return nil 142 | } 143 | 144 | // credentials := credentials.NewEnvCredentials() 145 | 146 | credentials := credentials.NewStaticCredentials( 147 | *credRes.Credentials.AccessKeyId, 148 | *credRes.Credentials.SecretKey, 149 | *credRes.Credentials.SessionToken, 150 | ) 151 | 152 | signer := v4.NewSigner(credentials) 153 | var b []byte 154 | if req.Body == nil { 155 | b = make([]byte, 0) 156 | } else { 157 | b2, err := ioutil.ReadAll(req.Body) 158 | if err != nil { 159 | return err 160 | } else { 161 | b = b2 162 | } 163 | } 164 | body := bytes.NewReader(b) 165 | /*hash := sha256.New() 166 | var hb []byte 167 | hash.Write(hb) 168 | req.Header.Add("X-Amz-Content-Sha256", string(hb))*/ 169 | _, err = signer.Sign(req, body, s.Service, s.Region, time.Now()) 170 | if err != nil { 171 | return err 172 | } 173 | return nil 174 | } 175 | -------------------------------------------------------------------------------- /lib/user/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "user", 5 | srcs = ["user.go"], 6 | importpath = "goclassifieds/lib/user", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/user/user.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | type PublicUserProfile struct { 4 | Id string 5 | UserName string 6 | } 7 | -------------------------------------------------------------------------------- /lib/utils/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "utils", 5 | srcs = [ 6 | "jwt.go", 7 | "uuid.go", 8 | "log.go", 9 | "compact.go", 10 | ], 11 | importpath = "goclassifieds/lib/utils", 12 | visibility = ["//visibility:public"], 13 | deps = [ 14 | "@com_github_dgrijalva_jwt_go//:jwt-go", 15 | "@com_github_gin_gonic_gin//:gin", 16 | "@com_github_google_uuid//:uuid", 17 | "@com_github_aws_aws_lambda_go//events", 18 | ], 19 | ) 20 | -------------------------------------------------------------------------------- /lib/utils/compact.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "errors" 7 | "os" 8 | ) 9 | 10 | // EncodeStringToFixedBytes encodes a string into a fixed number of bytes. 11 | func EncodeStringToFixedBytes(input string, fixedSize int) ([]byte, error) { 12 | if fixedSize <= 0 { 13 | return nil, errors.New("fixed size must be greater than zero") 14 | } 15 | 16 | encoded := []byte(input) 17 | 18 | // If the string is longer than the fixed size, truncate it. 19 | if len(encoded) > fixedSize { 20 | return encoded[:fixedSize], nil 21 | } 22 | 23 | // If the string is shorter, pad it with zero bytes. 24 | padding := make([]byte, fixedSize-len(encoded)) 25 | return append(encoded, padding...), nil 26 | } 27 | 28 | // DecodeFixedBytesToString decodes a fixed-size byte array back into a string. 29 | func DecodeFixedBytesToString(data []byte) string { 30 | return string(bytes.TrimRight(data, "\x00")) // Remove trailing zero bytes (padding) 31 | } 32 | 33 | // WriteFixedLengthStrings writes an array of strings to a binary file with fixed-length encoding. 34 | func WriteFixedLengthStrings(filename string, strings []string, fixedSize int) error { 35 | file, err := os.Create(filename) 36 | if err != nil { 37 | return err 38 | } 39 | defer file.Close() 40 | 41 | for _, s := range strings { 42 | encoded, err := EncodeStringToFixedBytes(s, fixedSize) 43 | if err != nil { 44 | return err 45 | } 46 | _, err = file.Write(encoded) 47 | if err != nil { 48 | return err 49 | } 50 | } 51 | return nil 52 | } 53 | 54 | // ReadFixedLengthString reads a single fixed-length string from a binary file at a given index. 55 | func ReadFixedLengthString(filename string, index int, fixedSize int) (string, error) { 56 | file, err := os.Open(filename) 57 | if err != nil { 58 | return "", err 59 | } 60 | defer file.Close() 61 | 62 | offset := index * fixedSize 63 | data := make([]byte, fixedSize) 64 | 65 | // Seek to the correct position in the file 66 | _, err = file.Seek(int64(offset), 0) 67 | if err != nil { 68 | return "", err 69 | } 70 | 71 | // Read the fixed-length string 72 | _, err = file.Read(data) 73 | if err != nil { 74 | return "", err 75 | } 76 | 77 | // Decode and return the string 78 | return DecodeFixedBytesToString(data), nil 79 | } 80 | 81 | func main() { 82 | // Fixed size for each string (128 bytes) 83 | fixedSize := 128 84 | 85 | // Strings to store in the file 86 | strings := []string{"Hello", "World", "Golang", "This is a longer string!"} 87 | 88 | // File to store the fixed-length encoded strings 89 | filename := "fixed_length_strings_128.bin" 90 | 91 | // Write the strings to the file 92 | err := WriteFixedLengthStrings(filename, strings, fixedSize) 93 | if err != nil { 94 | fmt.Printf("Error writing strings to file: %v\n", err) 95 | return 96 | } 97 | fmt.Println("Strings written to file successfully.") 98 | 99 | // Read specific strings from the file 100 | for i := 0; i < len(strings); i++ { 101 | result, err := ReadFixedLengthString(filename, i, fixedSize) 102 | if err != nil { 103 | fmt.Printf("Error reading string at index %d: %v\n", i, err) 104 | continue 105 | } 106 | fmt.Printf("String at index %d: %s\n", i, result) 107 | } 108 | 109 | // Random access: Read the 2nd string (index 1, zero-based) 110 | index := 1 111 | randomResult, err := ReadFixedLengthString(filename, index, fixedSize) 112 | if err != nil { 113 | fmt.Printf("Error reading string at index %d: %v\n", index, err) 114 | return 115 | } 116 | fmt.Printf("Random access result (index %d): %s\n", index, randomResult) 117 | } -------------------------------------------------------------------------------- /lib/utils/jwt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | 6 | jwt "github.com/dgrijalva/jwt-go" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func GetSubject(context *gin.Context) string { 11 | authPieces := strings.Split(context.GetHeader("Authorization"), " ") 12 | if len(authPieces) == 2 { 13 | claims := jwt.StandardClaims{} 14 | jwt.ParseWithClaims(authPieces[1], &claims, nil) 15 | return claims.Subject 16 | } 17 | return "" 18 | } 19 | -------------------------------------------------------------------------------- /lib/utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/aws/aws-lambda-go/events" 9 | ) 10 | 11 | type LogUsageLambdaInput struct { 12 | Intensities string 13 | Regions string 14 | Region string 15 | UserId string 16 | Username string 17 | Service string 18 | Resource string 19 | Path string 20 | RequestId string 21 | Repository string 22 | Organization string 23 | } 24 | 25 | func LogUsageForHttpRequest(req *events.APIGatewayProxyRequest) { 26 | 27 | _, hedged := req.Headers["x-hedge-region"] 28 | if hedged { 29 | log.Print("REPORT RequestId: " + req.RequestContext.RequestID + " Function: " + os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + " Path: " + req.Path + " Resource: " + req.Resource + " X-HEDGE-REGIONS: " + req.Headers["x-hedge-regions"] + " X-HEDGE-INTENSITIES: " + req.Headers["x-hedge-intensities"] + " X-HEDGE-REGION: " + req.Headers["x-hedge-region"] + " X-HEDGE-SERVICE: " + req.Headers["x-hedge-service"] + " UserId: " + GetUserIdFromHttpRequest(req) + " Username: " + GetUsernameFromHttpRequest(req)) 30 | } else { 31 | log.Print("REPORT RequestId: " + req.RequestContext.RequestID + " Function: " + os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + " Path: " + req.Path + " Resource: " + req.Resource) 32 | } 33 | 34 | } 35 | 36 | func LogUsageForWebsocketRequest(req *events.APIGatewayWebsocketProxyRequest) { 37 | 38 | _, hedged := req.Headers["x-hedge-region"] 39 | if hedged { 40 | log.Print("REPORT RequestId: " + req.RequestContext.RequestID + " Function: " + os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + " Path: " + req.Path + " Resource: " + req.Resource + " X-HEDGE-REGIONS: " + req.Headers["x-hedge-regions"] + " X-HEDGE-INTENSITIES: " + req.Headers["x-hedge-intensities"] + " X-HEDGE-REGION: " + req.Headers["x-hedge-region"] + " X-HEDGE-SERVICE: " + req.Headers["x-hedge-service"]) 41 | } else { 42 | log.Print("REPORT RequestId: " + req.RequestContext.RequestID + " Function: " + os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + " Path: " + req.Path + " Resource: " + req.Resource) 43 | } 44 | 45 | } 46 | 47 | func LogUsageForLambda() { 48 | 49 | log.Print("REPORT Function: " + os.Getenv("AWS_LAMBDA_FUNCTION_NAME")) 50 | 51 | } 52 | 53 | func LogUsageForLambdaWithInput(input *LogUsageLambdaInput) { 54 | 55 | log.Print("REPORT RequestId: " + input.RequestId + " Function: " + os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + " Path: " + input.Path + " Resource: " + input.Resource + " X-HEDGE-REGIONS: " + input.Regions + " X-HEDGE-INTENSITIES: " + input.Intensities + " X-HEDGE-REGION: " + input.Region + " X-HEDGE-SERVICE: " + input.Service + " UserId: " + input.UserId + " Username: " + input.Username + " Repository: " + input.Repository + " Organization: " + input.Organization) 56 | 57 | } 58 | 59 | func GetUserIdFromHttpRequest(req *events.APIGatewayProxyRequest) string { 60 | userId := "" 61 | log.Printf("claims are %v", req.RequestContext.Authorizer["claims"]) 62 | if req.RequestContext.Authorizer["claims"] != nil { 63 | userId = fmt.Sprint(req.RequestContext.Authorizer["claims"].(map[string]interface{})["sub"]) 64 | if userId == "" { 65 | userId = "" 66 | } 67 | } else if req.RequestContext.Authorizer["sub"] != nil { 68 | userId = req.RequestContext.Authorizer["sub"].(string) 69 | } 70 | return userId 71 | } 72 | 73 | func GetUsernameFromHttpRequest(req *events.APIGatewayProxyRequest) string { 74 | username := "" 75 | field := "cognito:username" 76 | /*if os.Getenv("STAGE") == "prod" { 77 | field = "cognito:username" 78 | }*/ 79 | log.Printf("claims are %v", req.RequestContext.Authorizer["claims"]) 80 | if req.RequestContext.Authorizer["claims"] != nil { 81 | username = fmt.Sprint(req.RequestContext.Authorizer["claims"].(map[string]interface{})[field]) 82 | if username == "" { 83 | username = "" 84 | } 85 | } else if req.RequestContext.Authorizer[field] != nil { 86 | username = req.RequestContext.Authorizer[field].(string) 87 | } 88 | return username 89 | } 90 | 91 | /*func GetUserIdFromWebsocketRequest(req *events.APIGatewayWebsocketProxyRequest) string { 92 | userId := "" 93 | log.Printf("claims are %v", req.RequestContext.Authorizer) 94 | if req.RequestContext.Authorizer["claims"] != nil { 95 | userId = fmt.Sprint(req.RequestContext.Authorizer["claims"].(map[string]interface{})["sub"]) 96 | if userId == "" { 97 | userId = "" 98 | } 99 | } else if req.RequestContext.Authorizer["sub"] != nil { 100 | userId = req.RequestContext.Authorizer["sub"].(string) 101 | } 102 | return userId 103 | } 104 | 105 | func GetUsernameFromWebsocketRequest(req *events.APIGatewayWebsocketProxyRequest) string { 106 | username := "" 107 | field := "cognito:username" 108 | log.Printf("claims are %v", req.RequestContext.Authorizer["claims"]) 109 | if req.RequestContext.Authorizer["claims"] != nil { 110 | username = fmt.Sprint(req.RequestContext.Authorizer["claims"].(map[string]interface{})[field]) 111 | if username == "" { 112 | username = "" 113 | } 114 | } else if req.RequestContext.Authorizer[field] != nil { 115 | username = req.RequestContext.Authorizer[field].(string) 116 | } 117 | return username 118 | }*/ 119 | -------------------------------------------------------------------------------- /lib/utils/uuid.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/google/uuid" 4 | 5 | func GenerateId() string { 6 | uuid, _ := uuid.NewUUID() 7 | return uuid.String() 8 | } 9 | -------------------------------------------------------------------------------- /lib/vocab/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "vocab", 5 | srcs = ["vocab.go"], 6 | importpath = "goclassifieds/lib/vocab", 7 | visibility = ["//visibility:public"], 8 | ) 9 | -------------------------------------------------------------------------------- /lib/vocab/vocab.go: -------------------------------------------------------------------------------- 1 | package vocab 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | ) 8 | 9 | type Vocabulary struct { 10 | Id string `form:"id" json:"id"` 11 | UserId string `form:"userId" json:"userId"` 12 | MachineName string `form:"machineName" json:"machineName" binding:"required" validate:"required"` 13 | HumanName string `form:"humanName" json:"humanName" binding:"required" validate:"required"` 14 | Terms []Term `form:"terms[]" json:"terms" binding:"required" validate:"required"` 15 | } 16 | 17 | type Term struct { 18 | Id string `form:"id" json:"id" binding:"required" validate:"required"` 19 | VocabularyId string `form:"vocabularyId" json:"vocabularyId" binding:"required" validate:"required"` 20 | ParentId string `form:"parentId" json:"parentId"` 21 | MachineName string `form:"machineName" json:"machineName" binding:"required" validate:"required"` 22 | HumanName string `form:"humanName" json:"humanName" binding:"required" validate:"required"` 23 | Weight int `form:"weight" json:"weight" binding:"required" validate:"required"` 24 | Group bool `form:"group" json:"group" binding:"required" validate:"required"` 25 | Selected bool `form:"selected" json:"selected" binding:"required" validate:"required"` 26 | Level int `form:"level" json:"level" binding:"required" validate:"required"` 27 | Children []Term `form:"children[]" json:"children"` 28 | } 29 | 30 | func ToEntity(vocab *Vocabulary) (map[string]interface{}, error) { 31 | var buf bytes.Buffer 32 | if err := json.NewEncoder(&buf).Encode(vocab); err != nil { 33 | log.Fatalf("Error encoding query: %s", err) 34 | } 35 | jsonData, err := json.Marshal(vocab) 36 | if err != nil { 37 | return nil, err 38 | } 39 | var entity map[string]interface{} 40 | err = json.Unmarshal(jsonData, &entity) 41 | return entity, nil 42 | } 43 | 44 | func FlattenTerm(term Term, selectedOnly bool) []Term { 45 | leafNodes := make([]Term, 0) 46 | if term.Children == nil || len(term.Children) == 0 { 47 | if !selectedOnly || term.Selected { 48 | leafNodes = append(leafNodes, term) 49 | } 50 | } else { 51 | for _, childTerm := range term.Children { 52 | flatChildren := FlattenTerm(childTerm, selectedOnly) 53 | for _, flatChild := range flatChildren { 54 | leafNodes = append(leafNodes, flatChild) 55 | } 56 | } 57 | } 58 | return leafNodes 59 | } 60 | -------------------------------------------------------------------------------- /lib/watttime/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library") 2 | 3 | go_library( 4 | name = "watttime", 5 | srcs = ["watttime.go"], 6 | importpath = "goclassifieds/lib/watttime", 7 | visibility = ["//visibility:public"], 8 | deps = [ 9 | ], 10 | ) 11 | -------------------------------------------------------------------------------- /lib/watttime/watttime.go: -------------------------------------------------------------------------------- 1 | package watttime 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io/ioutil" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type LoginInput struct { 13 | Username string 14 | Password string 15 | } 16 | 17 | type LoginOutput struct { 18 | Token string 19 | } 20 | 21 | type GridRegionsInput struct { 22 | Token string 23 | All bool 24 | } 25 | 26 | type GridRegionsOutput struct { 27 | GridRegions []GridRegion 28 | } 29 | 30 | type IndexInput struct { 31 | Token string 32 | Ba string 33 | } 34 | 35 | type IndexOutput struct { 36 | Freq string `json:"freq"` 37 | Ba string `json:"ba"` 38 | Percent int `json:"percent"` 39 | Moer float64 `json:"moer"` 40 | PointTime time.Time `json:"point_time"` 41 | } 42 | 43 | type IndexResponse struct { 44 | Freq string `json:"freq"` 45 | Ba string `json:"ba"` 46 | Percent string `json:"percent"` 47 | Moer string `json:"moer"` 48 | PointTime string `json:"point_time"` 49 | } 50 | 51 | type GridRegion struct { 52 | Ba string `json:"ba"` 53 | Name string `json:"name"` 54 | Access string `json:"access"` 55 | Datatype string `json:"datatype"` 56 | } 57 | 58 | type Result []interface{} 59 | 60 | type WattLoginResponse struct { 61 | Token string `json:"token"` 62 | } 63 | 64 | func Login(input *LoginInput) (*LoginOutput, error) { 65 | output := &LoginOutput{} 66 | 67 | h := http.Client{} 68 | req, _ := http.NewRequest("GET", "https://api2.watttime.org/v2/login", nil) 69 | req.SetBasicAuth(input.Username, input.Password) 70 | r, err := h.Do(req) 71 | if err != nil { 72 | return nil, err 73 | } else if r.Status != "200 OK" { 74 | return nil, errors.New("Status: " + r.Status) 75 | } 76 | 77 | defer r.Body.Close() 78 | body, err := ioutil.ReadAll(r.Body) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | wattRes := &WattLoginResponse{} 84 | err = json.Unmarshal(body, wattRes) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | output.Token = wattRes.Token 90 | 91 | return output, nil 92 | } 93 | 94 | func GridRegions(input *GridRegionsInput) (*GridRegionsOutput, error) { 95 | output := &GridRegionsOutput{} 96 | url := "https://api2.watttime.org/v2/ba-access" 97 | if input.All { 98 | url += "?all=true" 99 | } 100 | 101 | h := http.Client{} 102 | req, _ := http.NewRequest("GET", url, nil) 103 | req.Header.Set("Authorization", "Bearer "+input.Token) 104 | r, err := h.Do(req) 105 | if err != nil { 106 | return nil, err 107 | } else if r.Status != "200 OK" { 108 | return nil, errors.New("Status: " + r.Status) 109 | } 110 | 111 | defer r.Body.Close() 112 | body, err := ioutil.ReadAll(r.Body) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | var wattRes Result 118 | err = json.Unmarshal(body, &wattRes) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | typedResult := make([]GridRegion, len(wattRes)) 124 | for index, item := range wattRes { 125 | var gridRegion GridRegion 126 | b, _ := json.Marshal(item) 127 | json.Unmarshal(b, &gridRegion) 128 | typedResult[index] = gridRegion 129 | } 130 | 131 | output.GridRegions = typedResult 132 | 133 | return output, nil 134 | } 135 | 136 | func GetIndex(input *IndexInput) (*IndexOutput, error) { 137 | 138 | output := &IndexOutput{} 139 | url := "https://api2.watttime.org/index?ba=" + input.Ba 140 | 141 | h := http.Client{} 142 | req, _ := http.NewRequest("GET", url, nil) 143 | req.Header.Set("Authorization", "Bearer "+input.Token) 144 | r, err := h.Do(req) 145 | if err != nil { 146 | return nil, err 147 | } else if r.Status != "200 OK" { 148 | return nil, errors.New("Status: " + r.Status) 149 | } 150 | 151 | defer r.Body.Close() 152 | body, err := ioutil.ReadAll(r.Body) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | indexRes := &IndexResponse{} 158 | err = json.Unmarshal(body, indexRes) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | output.Freq = indexRes.Freq 164 | output.Ba = indexRes.Ba 165 | output.Percent, _ = strconv.Atoi(indexRes.Percent) 166 | output.Moer, _ = strconv.ParseFloat(indexRes.Moer, 64) 167 | output.PointTime, _ = time.Parse(time.RFC3339, indexRes.PointTime) 168 | 169 | return output, nil 170 | 171 | } 172 | -------------------------------------------------------------------------------- /manifests/entity-api.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: serving.knative.dev/v1 2 | kind: Service 3 | metadata: 4 | name: entity-api 5 | namespace: default 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - image: gcr.io//entity-api:latest # Replace with your actual image URL 11 | ports: 12 | - containerPort: 8080 13 | env: 14 | - name: ELASTIC_URL 15 | value: # Replace with your actual Elastic URL 16 | - name: BUCKET_NAME 17 | value: # Replace with your actual bucket name 18 | - name: STAGE 19 | value: dev # Replace with your desired stage (e.g., dev, prod) 20 | -------------------------------------------------------------------------------- /mappings/renewable-record.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "carbon": { 4 | "type": "float" 5 | }, 6 | "electricity": { 7 | "type": "float" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /node_deps.bzl: -------------------------------------------------------------------------------- 1 | def setup_node_dependencies(): 2 | # Load Node.js rules 3 | load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "npm_install") 4 | 5 | # Declare Node.js repositories 6 | node_repositories( 7 | node_version = "16.6.2", 8 | package_json = ["//:package.json"], 9 | ) 10 | 11 | # Install NPM packages 12 | npm_install( 13 | name = "npm", 14 | package_json = "//:package.json", 15 | package_lock_json = "//:package-lock.json", 16 | ) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goclassifieds", 3 | "devDependencies": { 4 | "@bazel/bazelisk": "^1.9.0", 5 | "@bazel/esbuild": "^3.6.0", 6 | "@bazel/ibazel": "^0.15.10", 7 | "@bazel/typescript": "3.8.0", 8 | "ajv": "^8.17.1", 9 | "cssjson": "^2.1.3", 10 | "matcher": "^5.0.0", 11 | "serverless-custom-packaging-plugin": "^0.1.2", 12 | "serverless-knative": "^0.6.0", 13 | "serverless-prune-plugin": "^2.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /serverless-versait.yml: -------------------------------------------------------------------------------- 1 | service: goclassifieds-versait-api 2 | frameworkVersion: '3' 3 | plugins: 4 | - serverless-prune-plugin 5 | - serverless-custom-packaging-plugin 6 | custom: 7 | githubToken: ${file(./private.${opt:stage, 'dev'}.json):githubToken} 8 | versaitUsername: ${file(./private.${opt:stage, 'dev'}.json):versaitUsername} 9 | webhookSecret: ${file(./private.${opt:stage, 'dev'}.json):webhookSecret} 10 | openAiApiKey: ${file(./private.${opt:stage, 'dev'}.json):openAiApiKey} 11 | provider: 12 | name: aws 13 | runtime: provided.al2023 14 | memorySize: 512 15 | timeout: 45 16 | #logs: 17 | #websocket: true 18 | httpApi: 19 | payload: '1.0' 20 | cors: true 21 | package: 22 | individually: true 23 | resources: 24 | Resources: 25 | VersaitRole: 26 | Type: AWS::IAM::Role 27 | Properties: 28 | Path: "/" 29 | RoleName: verait-${opt:region, 'us-east-1'}-lambdaRole 30 | AssumeRolePolicyDocument: 31 | Version: '2012-10-17' 32 | Statement: 33 | - Effect: Allow 34 | Principal: 35 | Service: 36 | - lambda.amazonaws.com 37 | - apigateway.amazonaws.com 38 | Action: sts:AssumeRole 39 | ManagedPolicyArns: 40 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 41 | Policies: 42 | - PolicyName: goclassifieds-versait-api-lambda 43 | PolicyDocument: 44 | Version: '2012-10-17' 45 | Statement: 46 | - Effect: Allow 47 | Action: 48 | - logs:CreateLogGroup 49 | - logs:CreateLogStream 50 | - logs:PutLogEvents 51 | - logs:DescribeLogGroups 52 | - logs:DescribeLogStreams 53 | - logs:GetLogEvents 54 | - logs:FilterLogEvents 55 | Resource: 56 | - 'Fn::Join': 57 | - ':' 58 | - 59 | - 'arn:aws:logs' 60 | - Ref: 'AWS::Region' 61 | - Ref: 'AWS::AccountId' 62 | - 'log-group:/aws/lambda/*:*:*' 63 | - Effect: "Allow" 64 | Action: 65 | - "lambda:InvokeFunction" 66 | Resource: "*" 67 | - Effect: "Allow" 68 | Action: 69 | - "execute-api:Invoke" 70 | - "execute-api:ManageConnections" 71 | Resource: "*" 72 | functions: 73 | VersaitApi: 74 | handler: bootstrap 75 | role: VersaitRole 76 | package: 77 | path: bazel-bin/api/versait 78 | artifact: .serverless/VersaitApi.zip 79 | libs: api/entity 80 | include_globs: 81 | - "**/bootstrap" 82 | - "**/*.json.tmpl" 83 | - "**/*.pem" 84 | environment: 85 | GITHUB_TOKEN: ${self:custom.githubToken} 86 | VERSAIT_USERNAME: ${self:custom.versaitUsername} 87 | OPENAI_API_KEY: ${self:custom.openAiApiKey} 88 | #WEBHOOK_SECRET: ${self:custom.webhookSecret} 89 | STAGE: ${opt:stage, 'dev'} 90 | events: 91 | - httpApi: 92 | path: /versait/{proxy+} 93 | method: POST -------------------------------------------------------------------------------- /serverless-watttime-demo.yml: -------------------------------------------------------------------------------- 1 | service: watttime-demo 2 | frameworkVersion: '3' 3 | plugins: 4 | - serverless-prune-plugin 5 | custom: 6 | wattTimeUsername: ${file(./private.${opt:stage, 'dev'}.json):wattTimeUsername} 7 | wattTimePassword: ${file(./private.${opt:stage, 'dev'}.json):wattTimePassword} 8 | provider: 9 | name: aws 10 | runtime: go1.x 11 | memorySize: 256 12 | timeout: 10 13 | package: 14 | individually: false 15 | patterns: 16 | - '!./**' 17 | - bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/watttime_demo/watttime_demo_/watttime_demo 18 | resources: 19 | Resources: 20 | WattTimeDemoRole: 21 | Type: AWS::IAM::Role 22 | Properties: 23 | Path: "/" 24 | RoleName: watttime-demo-${opt:region, 'us-east-1'}-lambdaRole 25 | AssumeRolePolicyDocument: 26 | Version: '2012-10-17' 27 | Statement: 28 | - Effect: Allow 29 | Principal: 30 | Service: 31 | - lambda.amazonaws.com 32 | - apigateway.amazonaws.com 33 | Action: sts:AssumeRole 34 | ManagedPolicyArns: 35 | - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole 36 | Policies: 37 | - PolicyName: watttime-demo-lambda 38 | PolicyDocument: 39 | Version: '2012-10-17' 40 | Statement: 41 | - Effect: Allow 42 | Action: 43 | - logs:CreateLogGroup 44 | - logs:CreateLogStream 45 | - logs:PutLogEvents 46 | - logs:DescribeLogGroups 47 | - logs:DescribeLogStreams 48 | - logs:GetLogEvents 49 | - logs:FilterLogEvents 50 | Resource: 51 | - 'Fn::Join': 52 | - ':' 53 | - 54 | - 'arn:aws:logs' 55 | - Ref: 'AWS::Region' 56 | - Ref: 'AWS::AccountId' 57 | - 'log-group:/aws/lambda/*:*:*' 58 | - Effect: "Allow" 59 | Action: 60 | - "lambda:InvokeFunction" 61 | Resource: "*" 62 | - Effect: "Allow" 63 | Action: 64 | - "execute-api:Invoke" 65 | - "execute-api:ManageConnections" 66 | Resource: "*" 67 | functions: 68 | WattTimeDemo: 69 | handler: bazel-out/darwin_arm64-fastbuild-ST-5fa8105c1248/bin/func/watttime_demo/watttime_demo_/watttime_demo 70 | role: WattTimeDemoRole 71 | environment: 72 | WATTTIME_USERNAME: ${self:custom.wattTimeUsername} 73 | WATTTIME_PASSWORD: ${self:custom.wattTimePassword} 74 | STAGE: ${opt:stage, 'dev'} --------------------------------------------------------------------------------