Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 65 additions & 9 deletions infra/api_gateway.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ resource "aws_api_gateway_rest_api" "lambda-api" {
name = "deckdeckgo-handler-rest-api"
}

###
### HANDLER
###

resource "aws_api_gateway_resource" "proxy-api" {
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
parent_id = "${aws_api_gateway_rest_api.lambda-api.root_resource_id}"
Expand All @@ -16,7 +20,7 @@ resource "aws_api_gateway_resource" "proxy" {

resource "aws_api_gateway_method" "proxy" {
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
resource_id = "${aws_api_gateway_resource.proxy.id}"
resource_id = "${aws_api_gateway_resource.proxy.id}" # TODO: -api?
http_method = "ANY"
authorization = "NONE"
}
Expand All @@ -32,31 +36,83 @@ resource "aws_api_gateway_integration" "lambda-api" {
uri = "${aws_lambda_function.api.invoke_arn}"
}

resource "aws_api_gateway_deployment" "lambda-api" {
resource "aws_lambda_permission" "lambda_permission" {
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.api.function_name}"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.lambda-api.execution_arn}/*/*/*"

depends_on = [
"aws_api_gateway_integration.lambda-api",
"aws_api_gateway_resource.proxy-api",
"aws_api_gateway_resource.proxy",
"aws_lambda_function.api",
]
}

###
### UNSPLASH
###

resource "aws_api_gateway_resource" "unsplash-proxy-root" {
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
stage_name = "beta"
parent_id = "${aws_api_gateway_rest_api.lambda-api.root_resource_id}"
path_part = "unsplash"
}

resource "aws_lambda_permission" "lambda_permission" {
resource "aws_api_gateway_resource" "unsplash-proxy" {
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
parent_id = "${aws_api_gateway_resource.unsplash-proxy-root.id}"
path_part = "{proxy+}"
}

resource "aws_api_gateway_method" "unsplash-proxy" {
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
resource_id = "${aws_api_gateway_resource.unsplash-proxy.id}"
http_method = "ANY"
authorization = "NONE"
}

# XXX: when redeploying, tweak the stage name
resource "aws_api_gateway_integration" "lambda-unsplash" {
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
resource_id = "${aws_api_gateway_method.unsplash-proxy.resource_id}"
http_method = "${aws_api_gateway_method.unsplash-proxy.http_method}"

integration_http_method = "POST"
type = "AWS_PROXY"
uri = "${aws_lambda_function.unsplash.invoke_arn}"
}

resource "aws_lambda_permission" "lambda_permission_unsplash" {
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.api.function_name}"
function_name = "${aws_lambda_function.unsplash.function_name}"
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.lambda-api.execution_arn}/*/*/*"

depends_on = [
"aws_lambda_function.api",
"aws_lambda_function.unsplash",
]
}

###
### GATEWAY GENERAL
###

resource "aws_api_gateway_deployment" "lambda-api" {
depends_on = [
"aws_api_gateway_integration.lambda-api",
"aws_api_gateway_resource.proxy-api",
"aws_api_gateway_resource.proxy",
"aws_api_gateway_resource.unsplash-proxy",
"aws_api_gateway_resource.unsplash-proxy-root",
]

rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
stage_name = "beta"
}

###############
# Enable CORS #
###############

# https://medium.com/@MrPonath/terraform-and-aws-api-gateway-a137ee48a8ac
resource "aws_api_gateway_method" "options_method" {
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
Expand Down
15 changes: 14 additions & 1 deletion infra/default.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
with { pkgs = import ./nix {}; };

rec
{ function =
{ function = # TODO: rename to handler
pkgs.runCommand "build-lambda" {}
''
cp ${pkgs.wai-lambda.wai-lambda-js-wrapper} main.js
Expand All @@ -12,9 +12,22 @@ rec
${pkgs.zip}/bin/zip -r $out/function.zip main.js main_hs google-public-keys.json
'';

function-unsplash =
pkgs.runCommand "build-lambda" {}
''
cp ${pkgs.wai-lambda.wai-lambda-js-wrapper} main.js
# Can't be called 'main' otherwise lambda tries to load it
cp "${unsplashProxyStatic}/bin/unsplash-proxy" main_hs
mkdir $out
${pkgs.zip}/bin/zip -r $out/function.zip main.js main_hs
'';

handlerStatic = pkgs.haskellPackagesStatic.deckdeckgo-handler;
handler = pkgs.haskellPackages.deckdeckgo-handler;

unsplashProxyStatic = pkgs.haskellPackagesStatic.unsplash-proxy;
unsplashProxy = pkgs.haskellPackages.unsplash-proxy;

dynamoJar = pkgs.runCommand "dynamodb-jar" { buildInputs = [ pkgs.gnutar ]; }
''
mkdir -p $out
Expand Down
1 change: 1 addition & 0 deletions infra/nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ with rec
mkPackage "deckdeckgo-handler" ../handler //
( mkPackage "wai-lambda" wai-lambda.wai-lambda-source ) //
( mkPackage "firebase-login" ../firebase-login ) //
( mkPackage "unsplash-proxy" ../unsplash-proxy ) //
{ jose = super.callCabal2nix "jose" sources.hs-jose {}; } //
{ port-utils = super.callCabal2nix "port-utils" sources.port-utils {}; } ;
};
Expand Down
1 change: 1 addition & 0 deletions infra/script/build-function
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# vim: filetype=sh
# TODO: rename to build-handler

set -euo pipefail

Expand Down
13 changes: 13 additions & 0 deletions infra/script/build-unsplash-proxy
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# vim: filetype=sh
# TODO: rename to build-handler

set -euo pipefail

out=$(nix-build --no-out-link -A function-unsplash)

cat <<JSON
{
"build_function_zip_path": "${out}/function.zip"
}
JSON
12 changes: 12 additions & 0 deletions infra/script/unsplash-client-id
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# vim: filetype=sh

set -euo pipefail

out=$(pass deckdeckgo/unsplash-client-id | head -n 1 | tr -d '\n')

cat <<JSON
{
"unsplash-client-id": "${out}"
}
JSON
10 changes: 9 additions & 1 deletion infra/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,16 @@ in
${pgutil.stop_pg}
}

function repl_handler() {
ghci handler/app/Test.hs handler/src/DeckGo/Handler.hs
}

function repl_unsplash() {
ghci unsplash-proxy/Main.hs
}

function repl() {
ghci handler/app/Test.hs handler/src/DeckGo/Handler.hs
repl_handler
}

'';
Expand Down
152 changes: 152 additions & 0 deletions infra/unsplash-proxy/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DataKinds #-}


module Main (main) where

import Control.Monad.IO.Class
import Data.Proxy
import Servant.API
import System.IO
import System.Environment (getEnv)
import qualified Data.Aeson as Aeson
import qualified Data.Text as T
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Conduit as HTTP
import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai as Wai
import qualified Network.Wai.Handler.Lambda as Lambda
import qualified Network.Wai.Middleware.Cors as Cors
import qualified Servant as Servant
import qualified Servant.Client as Client

-- https://api.unsplash.com/search/photos/?query={searchTerm}&page={next}&client_id={secret_key}
-- https://api.unsplash.com/photos/{photoId}/download/?client_id={secret_key}

newtype UnsplashQuery = UnsplashQuery { _unUnsplashQuery :: T.Text }
deriving newtype (FromHttpApiData, ToHttpApiData)

newtype UnsplashCliendId = UnsplashCliendId { _unUnsplashClientId :: T.Text }
deriving newtype (FromHttpApiData, ToHttpApiData)

newtype UnsplashPhotoId = UnsplashPhotoId { _unUnsplashPhotoId :: T.Text }
deriving newtype (FromHttpApiData, ToHttpApiData)

type ServerAPI = "unsplash" :> ClientAPI

type ClientAPI =
"search" :>
"photos" :>
QueryParam "query" UnsplashQuery :>
QueryParam "client_id" UnsplashCliendId :>
QueryParam "page" T.Text :>
Get '[JSON] Aeson.Value :<|>
"photos" :>
Capture "photoId" UnsplashPhotoId :>
"download" :>
QueryParam "client_id" UnsplashCliendId :>
Get '[JSON] Aeson.Value

runClient
:: MonadIO io
=> Client.ClientM a
-> io (Either Client.ServantError a)
runClient act = liftIO $ do
mgr <- HTTP.newManager HTTP.tlsManagerSettings
Client.runClientM act (clientEnv mgr)
where
clientEnv mgr =
Client.mkClientEnv
mgr (Client.BaseUrl Client.Https "api.unsplash.com" 443 "")

getUnsplashClientId :: IO UnsplashCliendId
getUnsplashClientId =
(UnsplashCliendId . T.pack) <$> getEnv "UNSPLASH_CLIENT_ID"

proxySearch
:: Maybe UnsplashQuery
-> Maybe UnsplashCliendId
-> Maybe T.Text
-> Servant.Handler Aeson.Value
proxySearch mq Nothing mPage = do
c <- liftIO getUnsplashClientId
liftIO $ putStrLn "proxySearch: calling"
runClient (proxySearch' mq (Just c) mPage) >>= \case
Left e -> do
liftIO $ print e
Servant.throwError Servant.err500
Right bs -> pure bs
proxySearch mq (Just c) mPage = do
liftIO $ putStrLn "proxySearch: calling"
runClient (proxySearch' mq (Just c) mPage) >>= \case
Left e -> do
liftIO $ print e
Servant.throwError Servant.err500
Right bs -> pure bs

proxySearch'
:: Maybe UnsplashQuery
-> Maybe UnsplashCliendId
-> Maybe T.Text
-> Client.ClientM Aeson.Value
proxyDownload'
:: UnsplashPhotoId
-> Maybe UnsplashCliendId
-> Client.ClientM Aeson.Value
proxySearch' :<|> proxyDownload' = Client.client clientApi

proxyDownload
:: UnsplashPhotoId
-> Maybe UnsplashCliendId
-> Servant.Handler Aeson.Value
proxyDownload photId Nothing = do
c <- liftIO getUnsplashClientId
liftIO $ putStrLn "proxyDownload: calling"
runClient (proxyDownload' photId (Just c)) >>= \case
Left e -> do
liftIO $ print e
Servant.throwError Servant.err500
Right bs -> pure bs
proxyDownload photId (Just c) = do
liftIO $ putStrLn "proxyDownload: calling"
runClient (proxyDownload' photId (Just c)) >>= \case
Left e -> do
liftIO $ print e
Servant.throwError Servant.err500
Right bs -> pure bs

serverApi :: Proxy ServerAPI
serverApi = Proxy

clientApi :: Proxy ClientAPI
clientApi = Proxy

server :: Servant.Server ServerAPI
server = proxySearch :<|> proxyDownload

application :: Wai.Application
application = Servant.serve serverApi server

main :: IO ()
main = do
hSetBuffering stdin LineBuffering
hSetBuffering stdout LineBuffering
Lambda.runSettings settings $ cors $ application
where
settings = Lambda.defaultSettings
{ Lambda.timeoutValue = 9 * 1000 * 1000 }

cors :: Wai.Middleware
cors = Cors.cors $
const $
Just Cors.simpleCorsResourcePolicy { Cors.corsMethods = methods }

methods :: [HTTP.Method]
methods =
[ "GET"
, "HEAD"
]
22 changes: 22 additions & 0 deletions infra/unsplash-proxy/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: unsplash-proxy
license: AGPL-3

executable:
main: Main.hs

dependencies:
- aeson
- base
- bytestring
- http-client
- http-client-tls
- http-conduit
- http-types
- servant
- servant-client
- servant-server
- text
- wai
- wai-cors
- wai-lambda
- warp
Loading