Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.

Commit e1fc8f9

Browse files
authored
Merge pull request #122 from deckgo/nm-unsplash
feat: proxy unsplash
2 parents a23c311 + ebea20f commit e1fc8f9

File tree

10 files changed

+381
-11
lines changed

10 files changed

+381
-11
lines changed

infra/api_gateway.tf

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ resource "aws_api_gateway_rest_api" "lambda-api" {
22
name = "deckdeckgo-handler-rest-api"
33
}
44

5+
###
6+
### HANDLER
7+
###
8+
59
resource "aws_api_gateway_resource" "proxy-api" {
610
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
711
parent_id = "${aws_api_gateway_rest_api.lambda-api.root_resource_id}"
@@ -16,7 +20,7 @@ resource "aws_api_gateway_resource" "proxy" {
1620

1721
resource "aws_api_gateway_method" "proxy" {
1822
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
19-
resource_id = "${aws_api_gateway_resource.proxy.id}"
23+
resource_id = "${aws_api_gateway_resource.proxy.id}" # TODO: -api?
2024
http_method = "ANY"
2125
authorization = "NONE"
2226
}
@@ -32,31 +36,83 @@ resource "aws_api_gateway_integration" "lambda-api" {
3236
uri = "${aws_lambda_function.api.invoke_arn}"
3337
}
3438

35-
resource "aws_api_gateway_deployment" "lambda-api" {
39+
resource "aws_lambda_permission" "lambda_permission" {
40+
action = "lambda:InvokeFunction"
41+
function_name = "${aws_lambda_function.api.function_name}"
42+
principal = "apigateway.amazonaws.com"
43+
source_arn = "${aws_api_gateway_rest_api.lambda-api.execution_arn}/*/*/*"
44+
3645
depends_on = [
37-
"aws_api_gateway_integration.lambda-api",
38-
"aws_api_gateway_resource.proxy-api",
39-
"aws_api_gateway_resource.proxy",
46+
"aws_lambda_function.api",
4047
]
48+
}
49+
50+
###
51+
### UNSPLASH
52+
###
4153

54+
resource "aws_api_gateway_resource" "unsplash-proxy-root" {
4255
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
43-
stage_name = "beta"
56+
parent_id = "${aws_api_gateway_rest_api.lambda-api.root_resource_id}"
57+
path_part = "unsplash"
4458
}
4559

46-
resource "aws_lambda_permission" "lambda_permission" {
60+
resource "aws_api_gateway_resource" "unsplash-proxy" {
61+
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
62+
parent_id = "${aws_api_gateway_resource.unsplash-proxy-root.id}"
63+
path_part = "{proxy+}"
64+
}
65+
66+
resource "aws_api_gateway_method" "unsplash-proxy" {
67+
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
68+
resource_id = "${aws_api_gateway_resource.unsplash-proxy.id}"
69+
http_method = "ANY"
70+
authorization = "NONE"
71+
}
72+
73+
# XXX: when redeploying, tweak the stage name
74+
resource "aws_api_gateway_integration" "lambda-unsplash" {
75+
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
76+
resource_id = "${aws_api_gateway_method.unsplash-proxy.resource_id}"
77+
http_method = "${aws_api_gateway_method.unsplash-proxy.http_method}"
78+
79+
integration_http_method = "POST"
80+
type = "AWS_PROXY"
81+
uri = "${aws_lambda_function.unsplash.invoke_arn}"
82+
}
83+
84+
resource "aws_lambda_permission" "lambda_permission_unsplash" {
4785
action = "lambda:InvokeFunction"
48-
function_name = "${aws_lambda_function.api.function_name}"
86+
function_name = "${aws_lambda_function.unsplash.function_name}"
4987
principal = "apigateway.amazonaws.com"
5088
source_arn = "${aws_api_gateway_rest_api.lambda-api.execution_arn}/*/*/*"
5189

5290
depends_on = [
53-
"aws_lambda_function.api",
91+
"aws_lambda_function.unsplash",
92+
]
93+
}
94+
95+
###
96+
### GATEWAY GENERAL
97+
###
98+
99+
resource "aws_api_gateway_deployment" "lambda-api" {
100+
depends_on = [
101+
"aws_api_gateway_integration.lambda-api",
102+
"aws_api_gateway_resource.proxy-api",
103+
"aws_api_gateway_resource.proxy",
104+
"aws_api_gateway_resource.unsplash-proxy",
105+
"aws_api_gateway_resource.unsplash-proxy-root",
54106
]
107+
108+
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"
109+
stage_name = "beta"
55110
}
56111

57112
###############
58113
# Enable CORS #
59114
###############
115+
60116
# https://medium.com/@MrPonath/terraform-and-aws-api-gateway-a137ee48a8ac
61117
resource "aws_api_gateway_method" "options_method" {
62118
rest_api_id = "${aws_api_gateway_rest_api.lambda-api.id}"

infra/default.nix

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
with { pkgs = import ./nix {}; };
22

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

15+
function-unsplash =
16+
pkgs.runCommand "build-lambda" {}
17+
''
18+
cp ${pkgs.wai-lambda.wai-lambda-js-wrapper} main.js
19+
# Can't be called 'main' otherwise lambda tries to load it
20+
cp "${unsplashProxyStatic}/bin/unsplash-proxy" main_hs
21+
mkdir $out
22+
${pkgs.zip}/bin/zip -r $out/function.zip main.js main_hs
23+
'';
24+
1525
handlerStatic = pkgs.haskellPackagesStatic.deckdeckgo-handler;
1626
handler = pkgs.haskellPackages.deckdeckgo-handler;
1727

28+
unsplashProxyStatic = pkgs.haskellPackagesStatic.unsplash-proxy;
29+
unsplashProxy = pkgs.haskellPackages.unsplash-proxy;
30+
1831
dynamoJar = pkgs.runCommand "dynamodb-jar" { buildInputs = [ pkgs.gnutar ]; }
1932
''
2033
mkdir -p $out

infra/nix/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ with rec
5656
mkPackage "deckdeckgo-handler" ../handler //
5757
( mkPackage "wai-lambda" wai-lambda.wai-lambda-source ) //
5858
( mkPackage "firebase-login" ../firebase-login ) //
59+
( mkPackage "unsplash-proxy" ../unsplash-proxy ) //
5960
{ jose = super.callCabal2nix "jose" sources.hs-jose {}; } //
6061
{ port-utils = super.callCabal2nix "port-utils" sources.port-utils {}; } ;
6162
};

infra/script/build-function

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env bash
22
# vim: filetype=sh
3+
# TODO: rename to build-handler
34

45
set -euo pipefail
56

infra/script/build-unsplash-proxy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
# vim: filetype=sh
3+
# TODO: rename to build-handler
4+
5+
set -euo pipefail
6+
7+
out=$(nix-build --no-out-link -A function-unsplash)
8+
9+
cat <<JSON
10+
{
11+
"build_function_zip_path": "${out}/function.zip"
12+
}
13+
JSON

infra/script/unsplash-client-id

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
# vim: filetype=sh
3+
4+
set -euo pipefail
5+
6+
out=$(pass deckdeckgo/unsplash-client-id | head -n 1 | tr -d '\n')
7+
8+
cat <<JSON
9+
{
10+
"unsplash-client-id": "${out}"
11+
}
12+
JSON

infra/shell.nix

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,16 @@ in
2828
${pgutil.stop_pg}
2929
}
3030
31+
function repl_handler() {
32+
ghci handler/app/Test.hs handler/src/DeckGo/Handler.hs
33+
}
34+
35+
function repl_unsplash() {
36+
ghci unsplash-proxy/Main.hs
37+
}
38+
3139
function repl() {
32-
ghci handler/app/Test.hs handler/src/DeckGo/Handler.hs
40+
repl_handler
3341
}
3442
3543
'';

infra/unsplash-proxy/Main.hs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
{-# LANGUAGE TypeOperators #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE LambdaCase #-}
4+
{-# LANGUAGE DerivingStrategies #-}
5+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6+
{-# LANGUAGE DataKinds #-}
7+
8+
9+
module Main (main) where
10+
11+
import Control.Monad.IO.Class
12+
import Data.Proxy
13+
import Servant.API
14+
import System.IO
15+
import System.Environment (getEnv)
16+
import qualified Data.Aeson as Aeson
17+
import qualified Data.Text as T
18+
import qualified Network.HTTP.Client as HTTP
19+
import qualified Network.HTTP.Conduit as HTTP
20+
import qualified Network.HTTP.Types as HTTP
21+
import qualified Network.Wai as Wai
22+
import qualified Network.Wai.Handler.Lambda as Lambda
23+
import qualified Network.Wai.Middleware.Cors as Cors
24+
import qualified Servant as Servant
25+
import qualified Servant.Client as Client
26+
27+
-- https://api.unsplash.com/search/photos/?query={searchTerm}&page={next}&client_id={secret_key}
28+
-- https://api.unsplash.com/photos/{photoId}/download/?client_id={secret_key}
29+
30+
newtype UnsplashQuery = UnsplashQuery { _unUnsplashQuery :: T.Text }
31+
deriving newtype (FromHttpApiData, ToHttpApiData)
32+
33+
newtype UnsplashCliendId = UnsplashCliendId { _unUnsplashClientId :: T.Text }
34+
deriving newtype (FromHttpApiData, ToHttpApiData)
35+
36+
newtype UnsplashPhotoId = UnsplashPhotoId { _unUnsplashPhotoId :: T.Text }
37+
deriving newtype (FromHttpApiData, ToHttpApiData)
38+
39+
type ServerAPI = "unsplash" :> ClientAPI
40+
41+
type ClientAPI =
42+
"search" :>
43+
"photos" :>
44+
QueryParam "query" UnsplashQuery :>
45+
QueryParam "client_id" UnsplashCliendId :>
46+
QueryParam "page" T.Text :>
47+
Get '[JSON] Aeson.Value :<|>
48+
"photos" :>
49+
Capture "photoId" UnsplashPhotoId :>
50+
"download" :>
51+
QueryParam "client_id" UnsplashCliendId :>
52+
Get '[JSON] Aeson.Value
53+
54+
runClient
55+
:: MonadIO io
56+
=> Client.ClientM a
57+
-> io (Either Client.ServantError a)
58+
runClient act = liftIO $ do
59+
mgr <- HTTP.newManager HTTP.tlsManagerSettings
60+
Client.runClientM act (clientEnv mgr)
61+
where
62+
clientEnv mgr =
63+
Client.mkClientEnv
64+
mgr (Client.BaseUrl Client.Https "api.unsplash.com" 443 "")
65+
66+
getUnsplashClientId :: IO UnsplashCliendId
67+
getUnsplashClientId =
68+
(UnsplashCliendId . T.pack) <$> getEnv "UNSPLASH_CLIENT_ID"
69+
70+
proxySearch
71+
:: Maybe UnsplashQuery
72+
-> Maybe UnsplashCliendId
73+
-> Maybe T.Text
74+
-> Servant.Handler Aeson.Value
75+
proxySearch mq Nothing mPage = do
76+
c <- liftIO getUnsplashClientId
77+
liftIO $ putStrLn "proxySearch: calling"
78+
runClient (proxySearch' mq (Just c) mPage) >>= \case
79+
Left e -> do
80+
liftIO $ print e
81+
Servant.throwError Servant.err500
82+
Right bs -> pure bs
83+
proxySearch mq (Just c) mPage = do
84+
liftIO $ putStrLn "proxySearch: calling"
85+
runClient (proxySearch' mq (Just c) mPage) >>= \case
86+
Left e -> do
87+
liftIO $ print e
88+
Servant.throwError Servant.err500
89+
Right bs -> pure bs
90+
91+
proxySearch'
92+
:: Maybe UnsplashQuery
93+
-> Maybe UnsplashCliendId
94+
-> Maybe T.Text
95+
-> Client.ClientM Aeson.Value
96+
proxyDownload'
97+
:: UnsplashPhotoId
98+
-> Maybe UnsplashCliendId
99+
-> Client.ClientM Aeson.Value
100+
proxySearch' :<|> proxyDownload' = Client.client clientApi
101+
102+
proxyDownload
103+
:: UnsplashPhotoId
104+
-> Maybe UnsplashCliendId
105+
-> Servant.Handler Aeson.Value
106+
proxyDownload photId Nothing = do
107+
c <- liftIO getUnsplashClientId
108+
liftIO $ putStrLn "proxyDownload: calling"
109+
runClient (proxyDownload' photId (Just c)) >>= \case
110+
Left e -> do
111+
liftIO $ print e
112+
Servant.throwError Servant.err500
113+
Right bs -> pure bs
114+
proxyDownload photId (Just c) = do
115+
liftIO $ putStrLn "proxyDownload: calling"
116+
runClient (proxyDownload' photId (Just c)) >>= \case
117+
Left e -> do
118+
liftIO $ print e
119+
Servant.throwError Servant.err500
120+
Right bs -> pure bs
121+
122+
serverApi :: Proxy ServerAPI
123+
serverApi = Proxy
124+
125+
clientApi :: Proxy ClientAPI
126+
clientApi = Proxy
127+
128+
server :: Servant.Server ServerAPI
129+
server = proxySearch :<|> proxyDownload
130+
131+
application :: Wai.Application
132+
application = Servant.serve serverApi server
133+
134+
main :: IO ()
135+
main = do
136+
hSetBuffering stdin LineBuffering
137+
hSetBuffering stdout LineBuffering
138+
Lambda.runSettings settings $ cors $ application
139+
where
140+
settings = Lambda.defaultSettings
141+
{ Lambda.timeoutValue = 9 * 1000 * 1000 }
142+
143+
cors :: Wai.Middleware
144+
cors = Cors.cors $
145+
const $
146+
Just Cors.simpleCorsResourcePolicy { Cors.corsMethods = methods }
147+
148+
methods :: [HTTP.Method]
149+
methods =
150+
[ "GET"
151+
, "HEAD"
152+
]

infra/unsplash-proxy/package.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: unsplash-proxy
2+
license: AGPL-3
3+
4+
executable:
5+
main: Main.hs
6+
7+
dependencies:
8+
- aeson
9+
- base
10+
- bytestring
11+
- http-client
12+
- http-client-tls
13+
- http-conduit
14+
- http-types
15+
- servant
16+
- servant-client
17+
- servant-server
18+
- text
19+
- wai
20+
- wai-cors
21+
- wai-lambda
22+
- warp

0 commit comments

Comments
 (0)