14 Commits
0.2.0 ... 0.6.0

Author SHA1 Message Date
9fc1aae5b5 web: Sort data in simple measure graphs. 2019-09-27 11:45:01 -05:00
9bcf0a8b12 api: Update parsing code for dates to handle truncated millisecond values coming from PostreSQL. 2019-09-27 11:09:58 -05:00
690c50754b Pull the TARGET_ENV variable from the environment first in the main Makefile. 2019-09-27 10:27:25 -05:00
d205bd3a37 Add CORS nginx configuration, Access-Control-Allow-Headers, clean target in Makefile. 2019-09-27 00:33:06 -05:00
bfcce67788 Rework deploy scripts to correctly build and handle multiple environments. 2019-09-26 23:23:32 -05:00
cfd5463b4d Add CDN cache invalidation to deploy scripts. 2019-09-25 10:48:46 -05:00
5c81d756df api: Refactor so all endpoints are CORS-aware. 2019-09-25 08:15:08 -05:00
cf60793395 api: WIP CORS supoport 2019-09-24 22:43:23 -05:00
0a8f701c3c Created terraform configuration to manage AWS infrastructure. 2019-09-24 22:40:08 -05:00
a4b798cec4 Bump version. 2019-05-19 18:31:33 -05:00
5f257e9b4a Target dev environment by default for deploys. 2019-05-19 18:30:53 -05:00
7e5827a7a2 api: Add Cache-Control header. 2019-05-19 18:28:38 -05:00
793dbcc611 Add support for parsing Postgres dates that only use one or two millisecond digits. 2019-05-19 18:28:22 -05:00
d37dc77490 Bump version. 2019-05-19 01:29:46 -05:00
29 changed files with 1356 additions and 205 deletions

3
.env.dev Normal file
View File

@ -0,0 +1,3 @@
VUE_APP_PM_API_BASE=https://pmapi-dev.jdb-labs.com/v0
VUE_APP_LOG_LEVEL=TRACE
VUE_APP_API_LOG_LEVEL=ERROR

3
.env.prod Normal file
View File

@ -0,0 +1,3 @@
VUE_APP_PM_API_BASE=https://pmapi.jdb-labs.com/v0
VUE_APP_LOG_LEVEL=INFO
VUE_APP_API_LOG_LEVEL=ERROR

View File

@ -1,5 +1,5 @@
VERSION:=$(shell git describe --always) VERSION:=$(shell git describe --always)
TARGET_ENV=prod TARGET_ENV?=dev
build: dist/personal-measure-api.tar.gz dist/personal-measure-web.tar.gz build: dist/personal-measure-api.tar.gz dist/personal-measure-web.tar.gz
@ -11,7 +11,7 @@ dist/personal-measure-api.tar.gz:
dist/personal-measure-web.tar.gz: dist/personal-measure-web.tar.gz:
-mkdir dist -mkdir dist
(cd web && npm run build) (TARGET_ENV=$(TARGET_ENV) ./set-env.sh make -C web build)
tar czf dist/personal-measure-web-${VERSION}.tar.gz -C web/dist . tar czf dist/personal-measure-web-${VERSION}.tar.gz -C web/dist .
cp dist/personal-measure-web-${VERSION}.tar.gz dist/personal-measure-web.tar.gz cp dist/personal-measure-web-${VERSION}.tar.gz dist/personal-measure-web.tar.gz
@ -27,6 +27,12 @@ deploy-web: dist/personal-measure-web.tar.gz
mkdir -p temp-deploy/personal-measure-web-${VERSION} mkdir -p temp-deploy/personal-measure-web-${VERSION}
tar xzf dist/personal-measure-web-${VERSION}.tar.gz -C temp-deploy/personal-measure-web-${VERSION} tar xzf dist/personal-measure-web-${VERSION}.tar.gz -C temp-deploy/personal-measure-web-${VERSION}
aws s3 sync temp-deploy/personal-measure-web-${VERSION} s3://pm.jdb-labs.com/$(TARGET_ENV)/webroot aws s3 sync temp-deploy/personal-measure-web-${VERSION} s3://pm.jdb-labs.com/$(TARGET_ENV)/webroot
TARGET_ENV=${TARGET_ENV} operations/invalidate-cdn-cache.sh
rm -r temp-deploy rm -r temp-deploy
deploy: deploy-api deploy-web deploy: deploy-api deploy-web
clean:
-rm -r dist
-rm api/personal_measure_api
-rm -r web/dist

View File

@ -3,5 +3,6 @@
"dbConnString":"host=localhost port=5500 dbname=personal_measure user=postgres password=password", "dbConnString":"host=localhost port=5500 dbname=personal_measure user=postgres password=password",
"debug":true, "debug":true,
"port":8081, "port":8081,
"pwdCost":11 "pwdCost":11,
"knownOrigins": [ "https://curl.localhost" ]
} }

View File

@ -1,5 +1,6 @@
{ {
"debug":false, "debug":false,
"port":80, "port":80,
"pwdCost":11 "pwdCost":11,
"knownOrigins": [ "https://pm.jdb-labs.com" ]
} }

View File

@ -34,6 +34,10 @@ proc loadConfig*(args: Table[string, docopt.Value] = initTable[string, docopt.Va
warn "Cannot read configuration file \"" & filePath & "\":\n\t" & warn "Cannot read configuration file \"" & filePath & "\":\n\t" &
getCurrentExceptionMsg() getCurrentExceptionMsg()
let knownOriginsArray =
if json.hasKey("knownOrigins"): json["knownOrigins"]
else: newJArray()
let cfg = CombinedConfig(docopt: args, json: json) let cfg = CombinedConfig(docopt: args, json: json)
result = PMApiConfig( result = PMApiConfig(
@ -41,7 +45,8 @@ proc loadConfig*(args: Table[string, docopt.Value] = initTable[string, docopt.Va
dbConnString: cfg.getVal("db-conn-string"), dbConnString: cfg.getVal("db-conn-string"),
debug: "true".startsWith(cfg.getVal("debug", "false").toLower()), debug: "true".startsWith(cfg.getVal("debug", "false").toLower()),
port: parseInt(cfg.getVal("port", "8080")), port: parseInt(cfg.getVal("port", "8080")),
pwdCost: cast[int8](parseInt(cfg.getVal("pwd-cost", "11")))) pwdCost: cast[int8](parseInt(cfg.getVal("pwd-cost", "11"))),
knownOrigins: toSeq(knownOriginsArray).mapIt(it.getStr))
proc initContext(args: Table[string, docopt.Value]): PMApiContext = proc initContext(args: Table[string, docopt.Value]): PMApiContext =

View File

@ -32,21 +32,47 @@ template halt(code: HttpCode,
result.matched = true result.matched = true
break allRoutes break allRoutes
template jsonResp(code: HttpCode, details: string = "", headers: RawHeaders = @{:} ) = template jsonResp(code: HttpCode, body: string = "", headersToSend: RawHeaders = @{:} ) =
let reqOrigin =
if request.headers.hasKey("Origin"): $(request.headers["Origin"])
else: ""
let corsHeaders =
if ctx.cfg.knownOrigins.contains(reqOrigin):
@{
"Access-Control-Allow-Origin": reqOrigin,
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": $(request.reqMethod),
"Access-Control-Allow-Headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
}
else: @{:}
halt( halt(
code, code,
headers & @{"Content-Type": JSON}, headersToSend & corsHeaders & @{
"Content-Type": JSON,
"Cache-Control": "no-cache"
},
body
)
template jsonResp(body: string) = jsonResp(Http200, body)
template statusResp(code: HttpCode, details: string = "", headersToSend: RawHeaders = @{:} ) =
jsonResp(
code,
$(%* { $(%* {
"statusCode": code.int, "statusCode": code.int,
"status": $code, "status": $code,
"details": details "details": details
}) }),
) headersToSend)
template json500Resp(ex: ref Exception, details: string = ""): void = template execptionResp(ex: ref Exception, details: string = ""): void =
when not defined(release): debug ex.getStackTrace() when not defined(release): debug ex.getStackTrace()
error details & ":\n" & ex.msg error details & ":\n" & ex.msg
jsonResp(Http500) statusResp(Http500)
# internal JSON parsing utils # internal JSON parsing utils
proc getIfExists(n: JsonNode, key: string): JsonNode = proc getIfExists(n: JsonNode, key: string): JsonNode =
@ -173,10 +199,10 @@ template checkAuth(requiresAdmin = false) =
try: session = extractSession(ctx, request) try: session = extractSession(ctx, request)
except: except:
debug "Auth failed: " & getCurrentExceptionMsg() debug "Auth failed: " & getCurrentExceptionMsg()
jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"}) statusResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"})
if requiresAdmin and not session.user.isAdmin: if requiresAdmin and not session.user.isAdmin:
jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"}) statusResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"})
proc start*(ctx: PMApiContext): void = proc start*(ctx: PMApiContext): void =
@ -186,12 +212,12 @@ proc start*(ctx: PMApiContext): void =
settings: settings:
port = Port(ctx.cfg.port) port = Port(ctx.cfg.port)
appName = "/api" appName = "/v0"
routes: routes:
get "/version": get "/version":
resp($(%("personal_measure_api v" & PM_API_VERSION)), JSON) jsonResp($(%("personal_measure_api v" & PM_API_VERSION)))
post "/auth-token": post "/auth-token":
@ -200,9 +226,9 @@ proc start*(ctx: PMApiContext): void =
let email = jsonBody.getOrFail("email").getStr let email = jsonBody.getOrFail("email").getStr
let pwd = jsonBody.getOrFail("password").getStr let pwd = jsonBody.getOrFail("password").getStr
let authToken = makeAuthToken(ctx, email, pwd) let authToken = makeAuthToken(ctx, email, pwd)
resp($(%authToken), JSON) jsonResp($(%authToken))
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except: jsonResp(Http401, getCurrentExceptionMsg()) except: statusResp(Http401, getCurrentExceptionMsg())
post "/change-pwd": post "/change-pwd":
checkAuth() checkAuth()
@ -215,15 +241,15 @@ proc start*(ctx: PMApiContext): void =
let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, session.user.salt) let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, session.user.salt)
session.user.hashedPwd = newHash.hash session.user.hashedPwd = newHash.hash
if ctx.db.updateUser(session.user): jsonResp(Http200) if ctx.db.updateUser(session.user): statusResp(Http200)
else: jsonResp(Http500, "unable to change pwd") else: statusResp(Http500, "unable to change pwd")
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except AuthError: jsonResp(Http401, getCurrentExceptionMsg()) except AuthError: statusResp(Http401, getCurrentExceptionMsg())
except: except:
error "internal error changing password: " & getCurrentExceptionMsg() error "internal error changing password: " & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
post "/change-pwd/@userId": post "/change-pwd/@userId":
checkAuth(true) checkAuth(true)
@ -234,22 +260,22 @@ proc start*(ctx: PMApiContext): void =
var user = ctx.db.getUser(parseUUID(@"userId")) var user = ctx.db.getUser(parseUUID(@"userId"))
let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, user.salt) let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, user.salt)
user.hashedPwd = newHash.hash user.hashedPwd = newHash.hash
if ctx.db.updateUser(user): jsonResp(Http200) if ctx.db.updateUser(user): statusResp(Http200)
else: jsonResp(Http500, "unable to change pwd") else: statusResp(Http500, "unable to change pwd")
except ValueError: jsonResp(Http400, "invalid UUID") except ValueError: statusResp(Http400, "invalid UUID")
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except AuthError: jsonResp(Http401, getCurrentExceptionMsg()) except AuthError: statusResp(Http401, getCurrentExceptionMsg())
except NotFoundError: jsonResp(Http404, "no such user") except NotFoundError: statusResp(Http404, "no such user")
except: except:
error "internal error changing password: " & getCurrentExceptionMsg() error "internal error changing password: " & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
get "/user": get "/user":
checkAuth() checkAuth()
resp(Http200, $(%session.user), JSON) jsonResp($(%session.user))
put "/user": put "/user":
checkAuth() checkAuth()
@ -262,18 +288,18 @@ proc start*(ctx: PMApiContext): void =
if jsonBody.hasKey("displayName"): if jsonBody.hasKey("displayName"):
updatedUser.displayName = jsonBody["displayName"].getStr() updatedUser.displayName = jsonBody["displayName"].getStr()
jsonResp(Http200, $(%ctx.db.updateUser(updatedUser))) statusResp(Http200, $(%ctx.db.updateUser(updatedUser)))
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except: except:
error "Could not update user information:\n\t" & getCurrentExceptionMsg() error "Could not update user information:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
get "/users": get "/users":
checkAuth(true) checkAuth(true)
resp(Http200, $(%ctx.db.getAllUsers())) jsonResp($(%ctx.db.getAllUsers()))
post "/users": post "/users":
checkAuth(true) checkAuth(true)
@ -290,18 +316,18 @@ proc start*(ctx: PMApiContext): void =
salt: pwdAndSalt.salt, salt: pwdAndSalt.salt,
isAdmin: false) isAdmin: false)
resp($(%ctx.db.createUser(newUser)), JSON) jsonResp($(%ctx.db.createUser(newUser)))
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except: except:
error "Could not create new user:\n\t" & getCurrentExceptionMsg() error "Could not create new user:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
get "/users/@userId": get "/users/@userId":
checkAuth(true) checkAuth(true)
resp(Http200, $(%ctx.db.getUser(parseUUID(@"userId")))) jsonResp($(%ctx.db.getUser(parseUUID(@"userId"))))
delete "/users/@userId": delete "/users/@userId":
checkAuth(true) checkAuth(true)
@ -310,18 +336,18 @@ proc start*(ctx: PMApiContext): void =
try: try:
let userId = parseUUID(@"userId") let userId = parseUUID(@"userId")
user = ctx.db.getUser(userId) user = ctx.db.getUser(userId)
except: jsonResp(Http404) except: statusResp(Http404)
try: try:
if not ctx.db.deleteUser(user): raiseEx "unable to delete user" if not ctx.db.deleteUser(user): raiseEx "unable to delete user"
jsonResp(Http200, "user " & user.email & " deleted") statusResp(Http200, "user " & user.email & " deleted")
except: jsonResp(Http500, getCurrentExceptionMsg()) except: statusResp(Http500, getCurrentExceptionMsg())
get "/api-tokens": get "/api-tokens":
checkAuth() checkAuth()
resp(Http200, $(%ctx.db.findApiTokensByUserId($session.user.id))) jsonResp($(%ctx.db.findApiTokensByUserId($session.user.id)))
post "/api-tokens": post "/api-tokens":
checkAuth() checkAuth()
@ -343,40 +369,40 @@ proc start*(ctx: PMApiContext): void =
let respToken = %newToken let respToken = %newToken
respToken["value"] = %tokenValue respToken["value"] = %tokenValue
resp($respToken, JSON) jsonResp($respToken)
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except AuthError: jsonResp(Http401, getCurrentExceptionMsg()) except AuthError: statusResp(Http401, getCurrentExceptionMsg())
except: except:
debug getCurrentExceptionMsg() debug getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
get "/api-tokens/@tokenId": get "/api-tokens/@tokenId":
checkAuth() checkAuth()
try: try:
resp(Http200, $(%ctx.db.getApiToken(parseUUID(@"tokenId")))) jsonResp($(%ctx.db.getApiToken(parseUUID(@"tokenId"))))
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: jsonResp(Http500) except: statusResp(Http500)
delete "/api-tokens/@tokenId": delete "/api-tokens/@tokenId":
checkAuth() checkAuth()
try: try:
let token = ctx.db.getApiToken(parseUUID(@"tokenId")) let token = ctx.db.getApiToken(parseUUID(@"tokenId"))
if ctx.db.deleteApiToken(token): jsonResp(Http200) if ctx.db.deleteApiToken(token): statusResp(Http200)
else: jsonResp(Http500) else: statusResp(Http500)
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: jsonResp(Http500) except: statusResp(Http500)
get "/measures": get "/measures":
checkAuth() checkAuth()
try: resp($(%ctx.db.findMeasuresByUserId($session.user.id)), JSON) try: jsonResp($(%ctx.db.findMeasuresByUserId($session.user.id)))
except: except:
error "unable to retrieve measures for user:\n\t" & getCurrentExceptionMsg() error "unable to retrieve measures for user:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
post "/measures": post "/measures":
checkAuth() checkAuth()
@ -406,45 +432,45 @@ proc start*(ctx: PMApiContext): void =
description: jsonBody.getIfExists("description").getStr(""), description: jsonBody.getIfExists("description").getStr(""),
config: config) config: config)
resp($(%ctx.db.createMeasure(newMeasure)), JSON) jsonResp($(%ctx.db.createMeasure(newMeasure)))
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except: except:
error "unable to create new measure:\n\t" & getCurrentExceptionMsg() error "unable to create new measure:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
get "/measures/@slug": get "/measures/@slug":
checkAuth() checkAuth()
try: resp($(%ctx.getMeasureForSlug(session.user.id, @"slug")), JSON) try: jsonResp($(%ctx.getMeasureForSlug(session.user.id, @"slug")))
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: except:
error "unable to look up a measure by id:\n\t" & getCurrentExceptionMsg() error "unable to look up a measure by id:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
delete "/measures/@slug": delete "/measures/@slug":
checkAuth() checkAuth()
try: try:
let measure = ctx.getMeasureForSlug(session.user.id, @"slug") let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
if ctx.db.deleteMeasure(measure): jsonResp(Http200) if ctx.db.deleteMeasure(measure): statusResp(Http200)
else: raiseEx "" else: raiseEx ""
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: except:
error "unable to delete a measure:\n\t" & getCurrentExceptionMsg() error "unable to delete a measure:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
get "/measure/@slug": get "/measure/@slug":
checkAuth() checkAuth()
try: try:
let measure = ctx.getMeasureForSlug(session.user.id, @"slug") let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
resp($(%ctx.db.findMeasurementsByMeasureId($measure.id)), JSON) jsonResp($(%ctx.db.findMeasurementsByMeasureId($measure.id)))
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: except:
error "unable to list measurements:\n\t" & getCurrentExceptionMsg() error "unable to list measurements:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
post "/measure/@slug": post "/measure/@slug":
checkAuth() checkAuth()
@ -463,29 +489,29 @@ proc start*(ctx: PMApiContext): void =
if jsonBody.hasKey("extData"): jsonBody["extData"] if jsonBody.hasKey("extData"): jsonBody["extData"]
else: newJObject()) else: newJObject())
resp($(%ctx.db.createMeasurement(newMeasurement)), JSON) jsonResp($(%ctx.db.createMeasurement(newMeasurement)))
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: except:
error "unable to add measurement:\n\t" & getCurrentExceptionMsg() error "unable to add measurement:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
get "/measure/@slug/@id": get "/measure/@slug/@id":
checkAuth() checkAuth()
try: try:
let measure = ctx.getMeasureForSlug(session.user.id, @"slug") let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
resp($(%ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id"))), JSON) jsonResp($(%ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id"))))
except ValueError: jsonResp(Http400, getCurrentExceptionMsg()) except ValueError: statusResp(Http400, getCurrentExceptionMsg())
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: except:
error "unable to retrieve measurement:\n\t" & getCurrentExceptionMsg() error "unable to retrieve measurement:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
put "/measure/@slug/@id": put "/measure/@slug/@id":
checkAuth() checkAuth()
@ -497,15 +523,15 @@ proc start*(ctx: PMApiContext): void =
if jsonBody.hasKey("value"): measurement.value = jsonBody["value"].getInt if jsonBody.hasKey("value"): measurement.value = jsonBody["value"].getInt
if jsonBody.hasKey("timestamp"): measurement.timestamp = jsonBody["timestamp"].getStr.parseIso8601 if jsonBody.hasKey("timestamp"): measurement.timestamp = jsonBody["timestamp"].getStr.parseIso8601
if jsonBody.hasKey("extData"): measurement.extData = jsonBody["extData"] if jsonBody.hasKey("extData"): measurement.extData = jsonBody["extData"]
resp($(%ctx.db.updateMeasurement(measurement)), JSON) jsonResp($(%ctx.db.updateMeasurement(measurement)))
except ValueError: jsonResp(Http400, getCurrentExceptionMsg()) except ValueError: statusResp(Http400, getCurrentExceptionMsg())
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: except:
error "unable to retrieve measurement:\n\t" & getCurrentExceptionMsg() error "unable to retrieve measurement:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
delete "/measure/@slug/@id": delete "/measure/@slug/@id":
checkAuth() checkAuth()
@ -513,16 +539,16 @@ proc start*(ctx: PMApiContext): void =
try: try:
let measure = ctx.getMeasureForSlug(session.user.id, @"slug") let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
let measurement = ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id")) let measurement = ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id"))
if ctx.db.deleteMeasurement(measurement): jsonResp(Http200) if ctx.db.deleteMeasurement(measurement): statusResp(Http200)
else: raiseEx "" else: raiseEx ""
except ValueError: jsonResp(Http400, getCurrentExceptionMsg()) except ValueError: statusResp(Http400, getCurrentExceptionMsg())
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg()) except NotFoundError: statusResp(Http404, getCurrentExceptionMsg())
except: except:
error "unable to delete measurement:\n\t" & getCurrentExceptionMsg() error "unable to delete measurement:\n\t" & getCurrentExceptionMsg()
jsonResp(Http500) statusResp(Http500)
post "/log": post "/log":
checkAuth() checkAuth()
@ -537,9 +563,9 @@ proc start*(ctx: PMApiContext): void =
stacktrace: jsonBody.getIfExists("stacktrace").getStr(""), stacktrace: jsonBody.getIfExists("stacktrace").getStr(""),
timestamp: jsonBody.getOrFail("timestamp").getStr.parseIso8601 timestamp: jsonBody.getOrFail("timestamp").getStr.parseIso8601
) )
resp(Http200, $(%ctx.db.createClientLogEntry(logEntry)), JSON) jsonResp($(%ctx.db.createClientLogEntry(logEntry)))
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except: jsonResp(Http500, getCurrentExceptionMsg()) except: statusResp(Http500, getCurrentExceptionMsg())
post "/log/batch": post "/log/batch":
checkAuth() checkAuth()
@ -555,15 +581,15 @@ proc start*(ctx: PMApiContext): void =
stacktrace: it.getIfExists("stacktrace").getStr(""), stacktrace: it.getIfExists("stacktrace").getStr(""),
timestamp: it.getOrFail("timestamp").getStr.parseIso8601 timestamp: it.getOrFail("timestamp").getStr.parseIso8601
)) ))
resp(Http200, $(%respMsgs), JSON) jsonResp($(%respMsgs))
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except: jsonResp(Http500, getCurrentExceptionMsg()) except: statusResp(Http500, getCurrentExceptionMsg())
post "/service/debug/stop": post "/service/debug/stop":
if not ctx.cfg.debug: jsonResp(Http404) if not ctx.cfg.debug: statusResp(Http404)
else: else:
let shutdownFut = sleepAsync(100) let shutdownFut = sleepAsync(100)
shutdownFut.callback = proc(): void = complete(stopFuture) shutdownFut.callback = proc(): void = complete(stopFuture)
resp($(%"shutting down"), JSON) jsonResp($(%"shutting down"))
waitFor(stopFuture) waitFor(stopFuture)

View File

@ -9,6 +9,7 @@ type
debug*: bool debug*: bool
port*: int port*: int
pwdCost*: int8 pwdCost*: int8
knownOrigins*: seq[string]
PMApiContext* = object PMApiContext* = object
cfg*: PMApiConfig cfg*: PMApiConfig
@ -23,7 +24,8 @@ proc `%`*(cfg: PMApiConfig): JsonNode =
"dbConnString": cfg.dbConnString, "dbConnString": cfg.dbConnString,
"debug": cfg.debug, "debug": cfg.debug,
"port": cfg.port, "port": cfg.port,
"pwdCost": cfg.pwdCost } "pwdCost": cfg.pwdCost,
"knownOrigins": cfg.knownOrigins }
template raiseEx*(errorType: type, reason: string): void = template raiseEx*(errorType: type, reason: string): void =
raise newException(errorType, reason) raise newException(errorType, reason)

View File

@ -1,6 +1,8 @@
import json, macros, options, sequtils, strutils, times, timeutils, unicode, import json, macros, options, sequtils, strutils, times, timeutils, unicode,
uuids uuids
import nre except toSeq
const UNDERSCORE_RUNE = "_".toRunes[0] const UNDERSCORE_RUNE = "_".toRunes[0]
const PG_TIMESTAMP_FORMATS = [ const PG_TIMESTAMP_FORMATS = [
"yyyy-MM-dd HH:mm:sszz", "yyyy-MM-dd HH:mm:sszz",
@ -70,6 +72,19 @@ proc parsePGDatetime*(val: string): DateTime =
for df in PG_TIMESTAMP_FORMATS: for df in PG_TIMESTAMP_FORMATS:
try: return val.parse(df) try: return val.parse(df)
except: errStr &= "\n" & getCurrentExceptionMsg() except: errStr &= "\n" & getCurrentExceptionMsg()
# PostgreSQL does not pad the millisecond value in a datetime out to three
# decimal points. If this is a value like `2019-09-29 12:00:00.5Z` We need to
# manually catch this and pad it out to something like
# `2019-09-29 12:00:00.500Z` so that we can parse it.
const millisTruncDatePattern = "(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.)(\\d{1,2})(.*)"
let match = val.match(re(millisTruncDatePattern))
if match.isSome:
let captures = match.get.captures
let reformatted = captures[0] & captures[1].alignLeft(3, '0') & captures[2]
try: return reformatted.parse(PG_TIMESTAMP_FORMATS[1])
except: errStr &= "\n" & getCurrentExceptionMsg()
raise newException(ValueError, "Cannot parse PG date. Tried:" & errStr) raise newException(ValueError, "Cannot parse PG date. Tried:" & errStr)
proc parseDbArray*(val: string): seq[string] = proc parseDbArray*(val: string): seq[string] =

View File

@ -1 +1 @@
const PM_API_VERSION* = "0.2.0" const PM_API_VERSION* = "0.6.0"

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
host="${PM_API_HOST:-localhost:8081}" api_base_url="${PM_API_BASE_URL:-http://localhost:8081}"
if [ $# -eq 1 ]; then if [ $# -eq 1 ]; then
url="$1" url="$1"
method="GET" method="GET"
@ -18,5 +18,7 @@ fi
curl -s -X "$method" \ curl -s -X "$method" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: $(cat credential)" \ -H "Authorization: $(cat credential)" \
"http://${host}/api/$url" \ -H "Origin: https://curl.localhost" \
-d "$data" "${api_base_url}/api/$url" \
-d "$data" \
-v

View File

@ -0,0 +1,28 @@
#!/bin/bash
echo "Looking up CloudFront distribution ID for Personal Measure ${TARGET_ENV} environment."
cloudfront_distribution_id=$(\
aws cloudfront list-distributions \
--query "DistributionList.Items[?starts_with(Comment, 'Personal Measure ${TARGET_ENV}')].Id | [0]" \
| sed -e 's/^"//' -e 's/"$//'
)
if [[ -z "${cloudfront_distribution_id}" ]]; then
>&2 echo "Unable to find CloudFront distribution for domain ${TARGET_ENV}."
exit 3
fi
echo "Found distribution ID ${cloudfront_distribution_id}."
echo "Invalidating the CloudFront cache for ${TARGET_ENV}."
invalidation_id=$(aws cloudfront create-invalidation \
--query 'Invalidation.Id' \
--distribution-id "${cloudfront_distribution_id}" \
--paths '/index.html')
if [[ $? -ne 0 || -z "${invalidation_id}" ]]; then
>&2 echo "Unable to create the CloudFront invalidation."
else
echo "Successfully created invalidation ${invalidation_id}."
fi
echo "Done."

View File

@ -0,0 +1,33 @@
server {
listen 80;
server_name pmapi-dev.jdb-labs.com;
return 301 https://pmapi-dev.jdb-labs.com$request_uri;
}
server {
listen 443;
server_name pmapi-dev.jdb-labs.com;
ssl on;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://pm-dev.jdb-labs.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
return 204;
}
proxy_pass http://localhost:8281;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@ -0,0 +1,33 @@
server {
listen 80;
server_name pmapi.jdb-labs.com;
return 301 https://pmapi.jdb-labs.com$request_uri;
}
server {
listen 443;
server_name pmapi.jdb-labs.com;
ssl on;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://pm.jdb-labs.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
return 204;
}
proxy_pass http://localhost:8280;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@ -0,0 +1,19 @@
### Variables
variable "aws_region" {
description = "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html"
default = "us-west-2" # Oregon
}
variable "deploy_bucket_name" {
description = "Name of the S3 bucket to store deployed artifacts, logs, etc."
default = "pm.jdb-labs.com"
}
#### Provider Configuration
provider "aws" {
region = var.aws_region
}

View File

@ -0,0 +1,102 @@
data "aws_iam_policy_document" "bucket_access_policy" {
statement {
actions = [ "s3:GetObject" ]
effect = "Allow"
resources = [ "${var.artifact_bucket.arn}/${var.environment}/webroot/*" ]
principals {
type = "AWS"
identifiers = [ "${aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn}" ]
}
}
statement {
actions = [ "s3:ListBucket" ]
effect = "Allow"
resources = [ "${var.artifact_bucket.arn}" ]
principals {
type = "AWS"
identifiers = [ "${aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn}" ]
}
}
}
output "oai_access_policy" {
value = data.aws_iam_policy_document.bucket_access_policy
}
locals {
env_domain_name = "pm${var.environment == "prod" ? "" : "-${var.environment}"}.jdb-labs.com"
}
resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {
comment = "OAI for Personal Measure {$var.environment} environment."
}
resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = "${var.artifact_bucket.bucket_regional_domain_name}"
origin_id = "S3-PersonalMeasure-${var.environment}"
origin_path = "/${var.environment}/webroot"
s3_origin_config {
origin_access_identity = "${aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path}"
}
}
enabled = true
is_ipv6_enabled = true
comment = "Personal Measure ${var.environment} distribution."
default_root_object = "/index.html"
logging_config {
include_cookies = false
bucket = "${var.artifact_bucket.bucket_domain_name}"
prefix = "${var.environment}/logs/cloudfront"
}
aliases = ["${local.env_domain_name}"]
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "S3-PersonalMeasure-${var.environment}"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 60 * 60 * 24 * 365 # cache for a year
max_ttl = 60 * 60 * 24 * 365 # cache for a year
compress = true
viewer_protocol_policy = "redirect-to-https"
}
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/index.html"
}
price_class = "PriceClass_100" # US and Canada only
restrictions {
geo_restriction {
restriction_type = "none"
}
}
tags = {
Environment = "${var.environment}"
}
viewer_certificate {
acm_certificate_arn = "${var.cloudfront_ssl_certificate_arn}"
ssl_support_method = "sni-only"
}
}

View File

@ -0,0 +1,13 @@
### Variables
variable "environment" {
description = "The short name of this deployed environment. For example: 'dev' or 'prod'. This short name will be used to name resources (CloudFront distributions, etc.)"
}
variable "artifact_bucket" {
description = "The aws_s3_bucket object representing the artifact bucket where deployed artifacts, logs, etc. live."
}
variable "cloudfront_ssl_certificate_arn" {
description = "ARN of the managed SSL certificate to use for this environment."
}

View File

@ -0,0 +1,30 @@
resource "aws_s3_bucket" "personal_measure" {
bucket = "${var.deploy_bucket_name}"
acl = "log-delivery-write"
}
module "dev_env" {
source = "./deployed_env"
environment = "dev"
artifact_bucket = aws_s3_bucket.personal_measure
cloudfront_ssl_certificate_arn = "arn:aws:acm:us-east-1:063932952339:certificate/48fe3ce0-4700-4eaa-b433-bb634f47934c"
}
module "prod_env" {
source = "./deployed_env"
environment = "prod"
artifact_bucket = aws_s3_bucket.personal_measure
cloudfront_ssl_certificate_arn = "arn:aws:acm:us-east-1:063932952339:certificate/48fe3ce0-4700-4eaa-b433-bb634f47934c"
}
data "aws_iam_policy_document" "cloudfront_access_policy" {
source_json = "${module.dev_env.oai_access_policy.json}"
override_json = "${module.prod_env.oai_access_policy.json}"
}
resource "aws_s3_bucket_policy" "personal_measure" {
bucket = "${aws_s3_bucket.personal_measure.id}"
policy = "${data.aws_iam_policy_document.cloudfront_access_policy.json}"
}

View File

@ -0,0 +1,547 @@
{
"version": 4,
"terraform_version": "0.12.9",
"serial": 13,
"lineage": "07ea4679-dcfc-ec03-69c0-9f3b3df53386",
"outputs": {},
"resources": [
{
"module": "module.prod_env",
"mode": "data",
"type": "aws_iam_policy_document",
"name": "bucket_access_policy",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "4164925389",
"json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n }\n ]\n}",
"override_json": null,
"policy_id": null,
"source_json": null,
"statement": [
{
"actions": [
"s3:GetObject"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*"
],
"sid": ""
},
{
"actions": [
"s3:ListBucket"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com"
],
"sid": ""
}
],
"version": "2012-10-17"
},
"depends_on": [
"aws_cloudfront_origin_access_identity.origin_access_identity"
]
}
]
},
{
"module": "module.dev_env",
"mode": "data",
"type": "aws_iam_policy_document",
"name": "bucket_access_policy",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "672870168",
"json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n }\n ]\n}",
"override_json": null,
"policy_id": null,
"source_json": null,
"statement": [
{
"actions": [
"s3:GetObject"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*"
],
"sid": ""
},
{
"actions": [
"s3:ListBucket"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com"
],
"sid": ""
}
],
"version": "2012-10-17"
},
"depends_on": [
"aws_cloudfront_origin_access_identity.origin_access_identity"
]
}
]
},
{
"mode": "data",
"type": "aws_iam_policy_document",
"name": "cloudfront_access_policy",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "1534115699",
"json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n }\n ]\n}",
"override_json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n }\n ]\n}",
"policy_id": null,
"source_json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n }\n ]\n}",
"statement": null,
"version": "2012-10-17"
},
"depends_on": [
"module.dev_env",
"module.prod_env"
]
}
]
},
{
"module": "module.prod_env",
"mode": "managed",
"type": "aws_cloudfront_distribution",
"name": "s3_distribution",
"provider": "provider.aws",
"instances": [
{
"schema_version": 1,
"attributes": {
"active_trusted_signers": {
"enabled": "false",
"items.#": "0"
},
"aliases": [
"pm.jdb-labs.com"
],
"arn": "arn:aws:cloudfront::063932952339:distribution/E331OLEUZMJYX2",
"cache_behavior": [],
"caller_reference": "terraform-20190924171430991900000002",
"comment": "Personal Measure prod distribution.",
"custom_error_response": [
{
"error_caching_min_ttl": null,
"error_code": 404,
"response_code": 200,
"response_page_path": "/index.html"
}
],
"default_cache_behavior": [
{
"allowed_methods": [
"GET",
"HEAD",
"OPTIONS"
],
"cached_methods": [
"GET",
"HEAD",
"OPTIONS"
],
"compress": true,
"default_ttl": 31536000,
"field_level_encryption_id": "",
"forwarded_values": [
{
"cookies": [
{
"forward": "none",
"whitelisted_names": null
}
],
"headers": null,
"query_string": false,
"query_string_cache_keys": null
}
],
"lambda_function_association": [],
"max_ttl": 31536000,
"min_ttl": 0,
"smooth_streaming": false,
"target_origin_id": "S3-PersonalMeasure-prod",
"trusted_signers": null,
"viewer_protocol_policy": "redirect-to-https"
}
],
"default_root_object": "/index.html",
"domain_name": "d1pydbw1mwi6dq.cloudfront.net",
"enabled": true,
"etag": "E39Y9O0I859AQB",
"hosted_zone_id": "Z2FDTNDATAQYW2",
"http_version": "http2",
"id": "E331OLEUZMJYX2",
"in_progress_validation_batches": 0,
"is_ipv6_enabled": true,
"last_modified_time": "2019-09-24 17:14:34.861 +0000 UTC",
"logging_config": [
{
"bucket": "pm.jdb-labs.com.s3.amazonaws.com",
"include_cookies": false,
"prefix": "prod/logs/cloudfront"
}
],
"ordered_cache_behavior": [],
"origin": [
{
"custom_header": [],
"custom_origin_config": [],
"domain_name": "pm.jdb-labs.com.s3.us-west-2.amazonaws.com",
"origin_id": "S3-PersonalMeasure-prod",
"origin_path": "/prod/webroot",
"s3_origin_config": [
{
"origin_access_identity": "origin-access-identity/cloudfront/EV7VQF8SH3HMM"
}
]
}
],
"origin_group": [],
"price_class": "PriceClass_100",
"restrictions": [
{
"geo_restriction": [
{
"locations": null,
"restriction_type": "none"
}
]
}
],
"retain_on_delete": false,
"status": "Deployed",
"tags": {
"Environment": "prod"
},
"viewer_certificate": [
{
"acm_certificate_arn": "arn:aws:acm:us-east-1:063932952339:certificate/48fe3ce0-4700-4eaa-b433-bb634f47934c",
"cloudfront_default_certificate": false,
"iam_certificate_id": "",
"minimum_protocol_version": "TLSv1",
"ssl_support_method": "sni-only"
}
],
"wait_for_deployment": true,
"web_acl_id": ""
},
"private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==",
"depends_on": [
"aws_cloudfront_origin_access_identity.origin_access_identity"
]
}
]
},
{
"module": "module.dev_env",
"mode": "managed",
"type": "aws_cloudfront_distribution",
"name": "s3_distribution",
"provider": "provider.aws",
"instances": [
{
"schema_version": 1,
"attributes": {
"active_trusted_signers": {
"enabled": "false",
"items.#": "0"
},
"aliases": [
"pm-dev.jdb-labs.com"
],
"arn": "arn:aws:cloudfront::063932952339:distribution/EYDKNEMGBYXK6",
"cache_behavior": [],
"caller_reference": "terraform-20190924171430991900000001",
"comment": "Personal Measure dev distribution.",
"custom_error_response": [
{
"error_caching_min_ttl": null,
"error_code": 404,
"response_code": 200,
"response_page_path": "/index.html"
}
],
"default_cache_behavior": [
{
"allowed_methods": [
"GET",
"HEAD",
"OPTIONS"
],
"cached_methods": [
"GET",
"HEAD",
"OPTIONS"
],
"compress": true,
"default_ttl": 31536000,
"field_level_encryption_id": "",
"forwarded_values": [
{
"cookies": [
{
"forward": "none",
"whitelisted_names": null
}
],
"headers": null,
"query_string": false,
"query_string_cache_keys": null
}
],
"lambda_function_association": [],
"max_ttl": 31536000,
"min_ttl": 0,
"smooth_streaming": false,
"target_origin_id": "S3-PersonalMeasure-dev",
"trusted_signers": null,
"viewer_protocol_policy": "redirect-to-https"
}
],
"default_root_object": "/index.html",
"domain_name": "d2gk6d79ot5fv3.cloudfront.net",
"enabled": true,
"etag": "E1DN3CB5IQVST8",
"hosted_zone_id": "Z2FDTNDATAQYW2",
"http_version": "http2",
"id": "EYDKNEMGBYXK6",
"in_progress_validation_batches": 0,
"is_ipv6_enabled": true,
"last_modified_time": "2019-09-24 17:14:32.614 +0000 UTC",
"logging_config": [
{
"bucket": "pm.jdb-labs.com.s3.amazonaws.com",
"include_cookies": false,
"prefix": "dev/logs/cloudfront"
}
],
"ordered_cache_behavior": [],
"origin": [
{
"custom_header": [],
"custom_origin_config": [],
"domain_name": "pm.jdb-labs.com.s3.us-west-2.amazonaws.com",
"origin_id": "S3-PersonalMeasure-dev",
"origin_path": "/dev/webroot",
"s3_origin_config": [
{
"origin_access_identity": "origin-access-identity/cloudfront/ENADNQSO0I1JY"
}
]
}
],
"origin_group": [],
"price_class": "PriceClass_100",
"restrictions": [
{
"geo_restriction": [
{
"locations": null,
"restriction_type": "none"
}
]
}
],
"retain_on_delete": false,
"status": "Deployed",
"tags": {
"Environment": "dev"
},
"viewer_certificate": [
{
"acm_certificate_arn": "arn:aws:acm:us-east-1:063932952339:certificate/48fe3ce0-4700-4eaa-b433-bb634f47934c",
"cloudfront_default_certificate": false,
"iam_certificate_id": "",
"minimum_protocol_version": "TLSv1",
"ssl_support_method": "sni-only"
}
],
"wait_for_deployment": true,
"web_acl_id": ""
},
"private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==",
"depends_on": [
"aws_cloudfront_origin_access_identity.origin_access_identity"
]
}
]
},
{
"module": "module.prod_env",
"mode": "managed",
"type": "aws_cloudfront_origin_access_identity",
"name": "origin_access_identity",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"caller_reference": "terraform-20190924170615555500000002",
"cloudfront_access_identity_path": "origin-access-identity/cloudfront/EV7VQF8SH3HMM",
"comment": "OAI for Personal Measure {$var.environment} environment.",
"etag": "E1XJOGSBHHRD9K",
"iam_arn": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM",
"id": "EV7VQF8SH3HMM",
"s3_canonical_user_id": "3a882d18f05e2fa5a3cabc208bcb8c0e2143166b56c0b8442f5b8b405c203859a3f525afcabc2e52dd1c9799d883a166"
},
"private": "bnVsbA=="
}
]
},
{
"module": "module.dev_env",
"mode": "managed",
"type": "aws_cloudfront_origin_access_identity",
"name": "origin_access_identity",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"caller_reference": "terraform-20190924170615555100000001",
"cloudfront_access_identity_path": "origin-access-identity/cloudfront/ENADNQSO0I1JY",
"comment": "OAI for Personal Measure {$var.environment} environment.",
"etag": "E1K0T63S2F5CYR",
"iam_arn": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY",
"id": "ENADNQSO0I1JY",
"s3_canonical_user_id": "6e965a9a0e9034badac65e1ac223e048b6d1b934d146abd32c49634489959a5ee1252e34fb643cd222dde425f2abfcd4"
},
"private": "bnVsbA=="
}
]
},
{
"mode": "managed",
"type": "aws_s3_bucket",
"name": "personal_measure",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"acceleration_status": "",
"acl": "log-delivery-write",
"arn": "arn:aws:s3:::pm.jdb-labs.com",
"bucket": "pm.jdb-labs.com",
"bucket_domain_name": "pm.jdb-labs.com.s3.amazonaws.com",
"bucket_prefix": null,
"bucket_regional_domain_name": "pm.jdb-labs.com.s3.us-west-2.amazonaws.com",
"cors_rule": [],
"force_destroy": false,
"hosted_zone_id": "Z3BJ6K6RIION7M",
"id": "pm.jdb-labs.com",
"lifecycle_rule": [],
"logging": [],
"object_lock_configuration": [],
"policy": null,
"region": "us-west-2",
"replication_configuration": [],
"request_payer": "BucketOwner",
"server_side_encryption_configuration": [],
"tags": {},
"versioning": [
{
"enabled": false,
"mfa_delete": false
}
],
"website": [],
"website_domain": null,
"website_endpoint": null
},
"private": "bnVsbA=="
}
]
},
{
"mode": "managed",
"type": "aws_s3_bucket_policy",
"name": "personal_measure",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"bucket": "pm.jdb-labs.com",
"id": "pm.jdb-labs.com",
"policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM\"\n }\n }\n ]\n}"
},
"private": "bnVsbA==",
"depends_on": [
"aws_s3_bucket.personal_measure",
"data.aws_iam_policy_document.cloudfront_access_policy"
]
}
]
}
]
}

View File

@ -0,0 +1,279 @@
{
"version": 4,
"terraform_version": "0.12.9",
"serial": 9,
"lineage": "07ea4679-dcfc-ec03-69c0-9f3b3df53386",
"outputs": {},
"resources": [
{
"module": "module.prod_env",
"mode": "data",
"type": "aws_iam_policy_document",
"name": "bucket_access_policy",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "1727217411",
"json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n }\n ]\n}",
"override_json": null,
"policy_id": null,
"source_json": null,
"statement": [
{
"actions": [
"s3:GetObject"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*"
],
"sid": ""
},
{
"actions": [
"s3:ListBucket"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com"
],
"sid": ""
}
],
"version": "2012-10-17"
},
"depends_on": [
"aws_cloudfront_origin_access_identity.origin_access_identity"
]
}
]
},
{
"module": "module.dev_env",
"mode": "data",
"type": "aws_iam_policy_document",
"name": "bucket_access_policy",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "3067586518",
"json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n }\n ]\n}",
"override_json": null,
"policy_id": null,
"source_json": null,
"statement": [
{
"actions": [
"s3:GetObject"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*"
],
"sid": ""
},
{
"actions": [
"s3:ListBucket"
],
"condition": [],
"effect": "Allow",
"not_actions": [],
"not_principals": [],
"not_resources": [],
"principals": [
{
"identifiers": [
"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY"
],
"type": "AWS"
}
],
"resources": [
"arn:aws:s3:::pm.jdb-labs.com"
],
"sid": ""
}
],
"version": "2012-10-17"
},
"depends_on": [
"aws_cloudfront_origin_access_identity.origin_access_identity"
]
}
]
},
{
"mode": "data",
"type": "aws_iam_policy_document",
"name": "cloudfront_access_policy",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"id": "754132408",
"json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n }\n ]\n}",
"override_json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n }\n ]\n}",
"policy_id": null,
"source_json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n }\n ]\n}",
"statement": null,
"version": "2012-10-17"
},
"depends_on": [
"module.dev_env",
"module.prod_env"
]
}
]
},
{
"module": "module.prod_env",
"mode": "managed",
"type": "aws_cloudfront_origin_access_identity",
"name": "origin_access_identity",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"caller_reference": "terraform-20190924170615555500000002",
"cloudfront_access_identity_path": "origin-access-identity/cloudfront/EV7VQF8SH3HMM",
"comment": "OAI for Personal Measure {$var.environment} environment.",
"etag": "E1XJOGSBHHRD9K",
"iam_arn": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity EV7VQF8SH3HMM",
"id": "EV7VQF8SH3HMM",
"s3_canonical_user_id": "3a882d18f05e2fa5a3cabc208bcb8c0e2143166b56c0b8442f5b8b405c203859a3f525afcabc2e52dd1c9799d883a166"
},
"private": "bnVsbA=="
}
]
},
{
"module": "module.dev_env",
"mode": "managed",
"type": "aws_cloudfront_origin_access_identity",
"name": "origin_access_identity",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"caller_reference": "terraform-20190924170615555100000001",
"cloudfront_access_identity_path": "origin-access-identity/cloudfront/ENADNQSO0I1JY",
"comment": "OAI for Personal Measure {$var.environment} environment.",
"etag": "E1K0T63S2F5CYR",
"iam_arn": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ENADNQSO0I1JY",
"id": "ENADNQSO0I1JY",
"s3_canonical_user_id": "6e965a9a0e9034badac65e1ac223e048b6d1b934d146abd32c49634489959a5ee1252e34fb643cd222dde425f2abfcd4"
},
"private": "bnVsbA=="
}
]
},
{
"mode": "managed",
"type": "aws_s3_bucket",
"name": "personal_measure",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"acceleration_status": "",
"acl": "log-delivery-write",
"arn": "arn:aws:s3:::pm.jdb-labs.com",
"bucket": "pm.jdb-labs.com",
"bucket_domain_name": "pm.jdb-labs.com.s3.amazonaws.com",
"bucket_prefix": null,
"bucket_regional_domain_name": "pm.jdb-labs.com.s3.us-west-2.amazonaws.com",
"cors_rule": [],
"force_destroy": false,
"hosted_zone_id": "Z3BJ6K6RIION7M",
"id": "pm.jdb-labs.com",
"lifecycle_rule": [],
"logging": [],
"object_lock_configuration": [],
"policy": null,
"region": "us-west-2",
"replication_configuration": [],
"request_payer": "BucketOwner",
"server_side_encryption_configuration": [],
"tags": {},
"versioning": [
{
"enabled": false,
"mfa_delete": false
}
],
"website": [],
"website_domain": null,
"website_endpoint": null
},
"private": "bnVsbA=="
}
]
},
{
"mode": "managed",
"type": "aws_s3_bucket_policy",
"name": "personal_measure",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"bucket": "pm.jdb-labs.com",
"id": "pm.jdb-labs.com",
"policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/dev/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_ENADNQSO0I1JY\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com/prod/webroot/*\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::pm.jdb-labs.com\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::cloudfront:user/CloudFront_Origin_Access_Identity_EV7VQF8SH3HMM\"\n }\n }\n ]\n}"
},
"private": "bnVsbA==",
"depends_on": [
"aws_s3_bucket.personal_measure",
"data.aws_iam_policy_document.cloudfront_access_policy"
]
}
]
}
]
}

8
set-env.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
if [[ -z $TARGET_ENV ]]; then
echo "TARGET_ENV variable is not set."
exit 1
fi
export $(grep -v '^#' .env.$TARGET_ENV | xargs )
"$@"

View File

@ -1,3 +0,0 @@
VUE_APP_PM_API_BASE=https://pm.jdb-labs.com/api
VUE_APP_LOG_LEVEL=INFO
VUE_APP_API_LOG_LEVEL=ERROR

1
web/.env.production Symbolic link
View File

@ -0,0 +1 @@
../.env.prod

156
web/package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "personal-measure-web", "name": "personal-measure-web",
"version": "0.1.0", "version": "0.4.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -2167,12 +2167,40 @@
"dev": true "dev": true
}, },
"axios": { "axios": {
"version": "0.18.0", "version": "0.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz",
"integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
"requires": { "requires": {
"follow-redirects": "^1.3.0", "follow-redirects": "1.5.10",
"is-buffer": "^1.1.5" "is-buffer": "^2.0.2"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"is-buffer": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
"integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
} }
}, },
"babel-code-frame": { "babel-code-frame": {
@ -5724,6 +5752,7 @@
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
"dev": true,
"requires": { "requires": {
"debug": "^3.2.6" "debug": "^3.2.6"
}, },
@ -5732,6 +5761,7 @@
"version": "3.2.6", "version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": { "requires": {
"ms": "^2.1.1" "ms": "^2.1.1"
} }
@ -5870,8 +5900,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -5892,14 +5921,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -5914,20 +5941,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -6044,8 +6068,7 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -6057,7 +6080,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -6072,7 +6094,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -6080,14 +6101,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -6106,7 +6125,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -6187,8 +6205,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -6200,7 +6217,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -6286,8 +6302,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -6323,7 +6338,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -6343,7 +6357,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -6387,14 +6400,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },
@ -6682,9 +6693,9 @@
"dev": true "dev": true
}, },
"handlebars": { "handlebars": {
"version": "4.1.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.2.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "integrity": "sha512-LuccMnDrKB72bbClEIucoBNAIMpqmWvIGSKNEngDcYFT6hlCq7ZoCWc26ZT9mr6tfWTJeTswSldoI5LOeezzDQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"neo-async": "^2.6.0", "neo-async": "^2.6.0",
@ -7406,7 +7417,8 @@
"is-buffer": { "is-buffer": {
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
}, },
"is-callable": { "is-callable": {
"version": "1.1.4", "version": "1.1.4",
@ -9506,9 +9518,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.11", "version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true "dev": true
}, },
"lodash._reinterpolate": { "lodash._reinterpolate": {
@ -9528,9 +9540,9 @@
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
}, },
"lodash.defaultsdeep": { "lodash.defaultsdeep": {
"version": "4.6.0", "version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz",
"integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=", "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==",
"dev": true "dev": true
}, },
"lodash.findindex": { "lodash.findindex": {
@ -9568,9 +9580,9 @@
"dev": true "dev": true
}, },
"lodash.merge": { "lodash.merge": {
"version": "4.6.1", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
}, },
"lodash.sortby": { "lodash.sortby": {
"version": "4.7.0", "version": "4.7.0",
@ -10015,9 +10027,9 @@
} }
}, },
"mixin-deep": { "mixin-deep": {
"version": "1.3.1", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
"integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true, "dev": true,
"requires": { "requires": {
"for-in": "^1.0.2", "for-in": "^1.0.2",
@ -10122,7 +10134,8 @@
"ms": { "ms": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
}, },
"multicast-dns": { "multicast-dns": {
"version": "6.2.3", "version": "6.2.3",
@ -12852,9 +12865,9 @@
"dev": true "dev": true
}, },
"set-value": { "set-value": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
"integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true, "dev": true,
"requires": { "requires": {
"extend-shallow": "^2.0.1", "extend-shallow": "^2.0.1",
@ -14321,38 +14334,15 @@
"dev": true "dev": true
}, },
"union-value": { "union-value": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
"integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true, "dev": true,
"requires": { "requires": {
"arr-union": "^3.1.0", "arr-union": "^3.1.0",
"get-value": "^2.0.6", "get-value": "^2.0.6",
"is-extendable": "^0.1.1", "is-extendable": "^0.1.1",
"set-value": "^0.4.3" "set-value": "^2.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"set-value": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
"integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
"is-plain-object": "^2.0.1",
"to-object-path": "^0.3.0"
}
}
} }
}, },
"uniq": { "uniq": {

View File

@ -1,6 +1,6 @@
{ {
"name": "personal-measure-web", "name": "personal-measure-web",
"version": "0.2.0", "version": "0.6.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@ -19,14 +19,14 @@
"@types/lodash.findindex": "^4.6.6", "@types/lodash.findindex": "^4.6.6",
"@types/lodash.merge": "^4.6.5", "@types/lodash.merge": "^4.6.5",
"apexcharts": "^3.6.5", "apexcharts": "^3.6.5",
"axios": "^0.18.0", "axios": "^0.18.1",
"js-cookie": "^2.2.0", "js-cookie": "^2.2.0",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"keen-ui": "^1.1.2", "keen-ui": "^1.1.2",
"lodash.assign": "^4.2.0", "lodash.assign": "^4.2.0",
"lodash.findindex": "^4.6.0", "lodash.findindex": "^4.6.0",
"lodash.keyby": "^4.6.0", "lodash.keyby": "^4.6.0",
"lodash.merge": "^4.6.1", "lodash.merge": "^4.6.2",
"moment": "^2.24.0", "moment": "^2.24.0",
"register-service-worker": "^1.5.2", "register-service-worker": "^1.5.2",
"vue": "^2.6.6", "vue": "^2.6.6",

View File

@ -22,11 +22,13 @@ export class SimpleDetails extends Vue {
}; };
private get measurementChartData(): ApexAxisChartSeries { private get measurementChartData(): ApexAxisChartSeries {
const measurementData = this.measurements || []; const measurementData = this.measurements.slice() || [];
return [{ return [{
name: this.measure.name, name: this.measure.name,
data: measurementData.map((m) => ({ x: m.timestamp.toISOString(), y: m.value })) data: measurementData
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime())
.map((m) => ({ x: m.timestamp.toISOString(), y: m.value }))
}]; }];
} }

View File

@ -16,11 +16,13 @@ export class SimpleSummaryGraph extends Vue {
}; };
private get measurementData(): ApexAxisChartSeries { private get measurementData(): ApexAxisChartSeries {
const measurementData = this.measurements || []; let measurementData = this.measurements.slice() || [];
return [{ return [{
name: this.measure.name, name: this.measure.name,
data: measurementData.map((m) => ({ x: m.timestamp.toISOString(), y: m.value })) data: measurementData
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime())
.map((m) => ({ x: m.timestamp.toISOString(), y: m.value }))
}]; }];
} }
} }

View File

@ -8,7 +8,10 @@ const VERSION = {
module.exports = { module.exports = {
devServer: { devServer: {
proxy: { proxy: {
'/api': { target: 'http://localhost:8081' } '/api': {
pathRewrite: { '^/api': '/v0' },
target: 'http://localhost:8081'
}
}, },
host: 'localhost', host: 'localhost',
disableHostCheck: true disableHostCheck: true