Implementation of /measure, /measurements APIs.
This commit is contained in:
parent
33eff538f8
commit
02e0d1cae1
22
api/api.txt
22
api/api.txt
@ -3,23 +3,27 @@ Personal Measure API
|
||||
|
||||
### Measure
|
||||
|
||||
☐ GET /measures Get a list of all defined measures for this user.
|
||||
☐ POST /measures Create a new measure (post the definition).
|
||||
☐ GET /measures/<measure-slug> Get the definition for a specific measure.
|
||||
☐ DELETE /measures/<measure-slug> Delete a measure (and all values associated with it).
|
||||
☑ GET /measures Get a list of all defined measures for this user.
|
||||
☑ POST /measures Create a new measure (post the definition).
|
||||
☑ GET /measures/<measure-slug> Get the definition for a specific measure.
|
||||
☑ DELETE /measures/<measure-slug> Delete a measure (and all values associated with it).
|
||||
|
||||
### Values
|
||||
|
||||
☐ GET /<measure-slug> Get a list of values for a measure.
|
||||
☐ POST /<measure-slug> Add a new value for a measure.
|
||||
☐ GET /<measure-slug>/<id> Get the details for a specific value by id.
|
||||
☐ PUT /<measure-slug>/<id> Update a value by id.
|
||||
☐ DELETE /<measure-slug> Delete a value by id.
|
||||
☑ GET /measure/<measure-slug> Get a list of measurements for a measure.
|
||||
☑ POST /measure/<measure-slug> Add a new measurements for a measure.
|
||||
☑ GET /measure/<measure-slug>/<id> Get the details for a specific measurements by id.
|
||||
☐ PUT /measure/<measure-slug>/<id> Update a measurements by id.
|
||||
☑ DELETE /measure/<measure-slug>/<id> Delete a measurements by id.
|
||||
|
||||
### Auth
|
||||
|
||||
☑ GET /auth-token Given a valid username/password combo, get an auth token.
|
||||
☑ GET /user Given a valid auth token, return the user details.
|
||||
☑ POST /user Given a valid auth token for an admin user, create a new user account.
|
||||
☑ DELETE /user Given a valid auth token for an admin user, delete a user account.
|
||||
☑ POST /change-pwd Given a valid auth token and a valid password, change the password
|
||||
☑ POST /change-pwd/<id> Given a valid auth token for an admin user change the given user's password
|
||||
☑ GET /api-tokens List api tokens.
|
||||
☑ DELETE /api-tokens/<id> Delete a specific api token.
|
||||
☑ POST /api-tokens With a valid session, create a new api token.
|
||||
|
BIN
api/src/main/nim/personal_measure_api
Executable file
BIN
api/src/main/nim/personal_measure_api
Executable file
Binary file not shown.
@ -5,8 +5,10 @@ import personal_measure_apipkg/configuration
|
||||
import personal_measure_apipkg/service
|
||||
import personal_measure_apipkg/version
|
||||
|
||||
# temp for testing
|
||||
import personal_measure_apipkg/db
|
||||
import personal_measure_apipkg/models
|
||||
import bcrypt
|
||||
|
||||
const DEFAULT_CONFIG = PMApiConfig(
|
||||
authSecret: "change me",
|
||||
@ -73,19 +75,19 @@ Options:
|
||||
|
||||
# Initialize our service context
|
||||
let args = docopt(doc, version = PM_API_VERSION)
|
||||
echo $args
|
||||
let ctx = initContext(args)
|
||||
|
||||
if args["hashpwd"]:
|
||||
if args["--salt"]:
|
||||
echo hashPwd($args["<password>"], $args["--salt"])
|
||||
echo hashWithSalt($args["<password>"], $args["--salt"])
|
||||
else:
|
||||
let cost = ctx.cfg.pwdCost
|
||||
echo hashPwd($args["<password>"], cast[int8](cost))
|
||||
|
||||
if args["test"]:
|
||||
echo "test"
|
||||
echo ctx.db.getUsersByEmail("jonathan@jdbernard.com")
|
||||
echo ctx.db.findUsersByEmail("jonathan@jdbernard.com")
|
||||
echo genSalt(ctx.cfg.pwdCost)
|
||||
|
||||
if args["serve"]: start(ctx)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import asyncdispatch, base64, jester, json, jwt, logging, options, sequtils,
|
||||
strutils, times, uuids
|
||||
from unicode import capitalize
|
||||
import timeutils except `<`
|
||||
|
||||
import ./db, ./configuration, ./models, ./service, ./version
|
||||
@ -47,6 +48,17 @@ template json500Resp(ex: ref Exception, details: string = ""): void =
|
||||
error details & ":\n" & ex.msg
|
||||
jsonResp(Http500)
|
||||
|
||||
# internal JSON parsing utils
|
||||
proc getIfExists(n: JsonNode, key: string): JsonNode =
|
||||
## convenience method to get a key from a JObject or return null
|
||||
result = if n.hasKey(key): n[key]
|
||||
else: newJNull()
|
||||
|
||||
proc getOrFail(n: JsonNode, key: string, objName: string = ""): JsonNode =
|
||||
## convenience method to get a key from a JObject or raise an exception
|
||||
if not n.hasKey(key): raiseEx BadRequestError, objName & " missing key '" & key & "'"
|
||||
return n[key]
|
||||
|
||||
proc toJWT*(ctx: PMApiContext, session: Session): string =
|
||||
## Make a JST token for this session.
|
||||
var jwt = JWT(
|
||||
@ -78,25 +90,21 @@ proc fromJWT*(ctx: PMApiContext, strTok: string): Session =
|
||||
expires: fromUnix(jwt.claims["exp"].node.num))
|
||||
|
||||
proc fromApiToken*(ctx: PMApiContext, strTok: string): Session =
|
||||
info "fromApiToken"
|
||||
let pairs = strTok.decode.split(":")
|
||||
let email = pairs[0]
|
||||
let apiTokVal = pairs[1]
|
||||
|
||||
info "email: " & email & "\tapiTokVal: " & apiTokVal
|
||||
# Look up the user
|
||||
var user: User
|
||||
try:
|
||||
let users = ctx.db.getUsersByEmail(email)
|
||||
let users = ctx.db.findUsersByEmail(email)
|
||||
if users.len != 1: raiseEx ""
|
||||
user = users[0]
|
||||
info "Found user: " & $user
|
||||
except: raiseEx "invalid username or password"
|
||||
|
||||
# look for the ApiToken record, hashing the token provided with the user's salt
|
||||
let hashedToken = hashPwd(apiTokVal, user.salt)
|
||||
echo "Hashed token: " & hashedToken
|
||||
let foundTokens = ctx.db.getApiTokensByHashedToken(hashedToken)
|
||||
let hashed = hashWithSalt(apiTokVal, user.salt)
|
||||
let foundTokens = ctx.db.findApiTokensByHashedToken(hashed.hash)
|
||||
if foundTokens.len != 1: raiseEx "invalid username or password"
|
||||
|
||||
let apiToken = foundTokens[0]
|
||||
@ -137,23 +145,23 @@ proc makeAuthToken*(ctx: PMApiContext, email, pwd: string): string =
|
||||
## token string.
|
||||
|
||||
if email.len == 0 or pwd.len == 0:
|
||||
raiseEx "fields 'username' and 'password' required"
|
||||
raiseEx AuthError, "fields 'username' and 'password' required"
|
||||
|
||||
# find the user record
|
||||
var user: User
|
||||
try:
|
||||
let users = ctx.db.getUsersByEmail(email)
|
||||
let users = ctx.db.findUsersByEmail(email)
|
||||
if users.len != 1: raiseEx ""
|
||||
user = users[0]
|
||||
except: raiseEx "invalid username or password"
|
||||
except: raiseEx AuthError, "invalid username or password"
|
||||
|
||||
if not validatePwd(user, pwd): raiseEx "invalid username or password"
|
||||
if not validatePwd(user, pwd): raiseEx AuthError, "invalid username or password"
|
||||
|
||||
let session = newSession(user)
|
||||
|
||||
result = toJWT(ctx, session)
|
||||
|
||||
template checkAuth() =
|
||||
template checkAuth(requiresAdmin = false) =
|
||||
## Check this request for authentication and authorization information.
|
||||
## Injects the session into the running context. If the request is not
|
||||
## authorized, this template returns an appropriate 401 response.
|
||||
@ -165,6 +173,9 @@ template checkAuth() =
|
||||
debug "Auth failed: " & getCurrentExceptionMsg()
|
||||
jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"})
|
||||
|
||||
if requiresAdmin and not session.user.isAdmin:
|
||||
jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"})
|
||||
|
||||
proc start*(ctx: PMApiContext): void =
|
||||
|
||||
if ctx.cfg.debug: setLogFilter(lvlDebug)
|
||||
@ -181,48 +192,128 @@ proc start*(ctx: PMApiContext): void =
|
||||
resp($(%("personal_measure_api v" & PM_API_VERSION)), JSON)
|
||||
|
||||
post "/auth-token":
|
||||
var email, pwd: string
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
email = jsonBody["email"].getStr
|
||||
pwd = jsonBody["password"].getStr
|
||||
except: jsonResp(Http400)
|
||||
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
let email = jsonBody.getOrFail("email").getStr
|
||||
let pwd = jsonBody.getOrFail("password").getStr
|
||||
let authToken = makeAuthToken(ctx, email, pwd)
|
||||
resp($(%authToken), JSON)
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except: jsonResp(Http401, getCurrentExceptionMsg())
|
||||
|
||||
post "/change-pwd":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
|
||||
if not validatePwd(session.user, jsonBody.getOrFail("oldPassword").getStr):
|
||||
raiseEx AuthError, "old password is incorrect"
|
||||
|
||||
let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, session.user.salt)
|
||||
session.user.hashedPwd = newHash.hash
|
||||
if ctx.db.updateUser(session.user): jsonResp(Http200)
|
||||
else: jsonResp(Http500, "unable to change pwd")
|
||||
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except AuthError: jsonResp(Http401, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "internal error changing password: " & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
post "/change-pwd/@userId":
|
||||
checkAuth(true)
|
||||
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
|
||||
var user = ctx.db.getUser(parseUUID(@"userId"))
|
||||
let newHash = hashWithSalt(jsonBody.getOrFail("newPassword").getStr, user.salt)
|
||||
user.hashedPwd = newHash.hash
|
||||
if ctx.db.updateUser(user): jsonResp(Http200)
|
||||
else: jsonResp(Http500, "unable to change pwd")
|
||||
|
||||
except ValueError: jsonResp(Http400, "invalid UUID")
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except AuthError: jsonResp(Http401, getCurrentExceptionMsg())
|
||||
except NotFoundError: jsonResp(Http404, "no such user")
|
||||
except:
|
||||
error "internal error changing password: " & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
get "/user":
|
||||
checkAuth()
|
||||
|
||||
resp(Http200, $(%session.user), JSON)
|
||||
|
||||
post "/users":
|
||||
checkAuth(true)
|
||||
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
|
||||
let pwdAndSalt = jsonBody.getOrFail("password").getStr.hashPwd(ctx.cfg.pwdCost)
|
||||
|
||||
let newUser = User(
|
||||
displayName: jsonBody.getOrFail("displayName").getStr,
|
||||
email: jsonBody.getOrFail("email").getStr,
|
||||
hashedPwd: pwdAndSalt.pwd,
|
||||
salt: pwdAndSalt.salt,
|
||||
isAdmin: false)
|
||||
|
||||
resp($(%ctx.db.createUser(newUser)), JSON)
|
||||
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "Could not create new user:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
delete "/users/@userId":
|
||||
checkAuth(true)
|
||||
|
||||
var user: User
|
||||
try:
|
||||
let userId = parseUUID(@"userId")
|
||||
user = ctx.db.getUser(userId)
|
||||
except: jsonResp(Http404)
|
||||
|
||||
try:
|
||||
if not ctx.db.deleteUser(user): raiseEx "unable to delete user"
|
||||
except: jsonResp(Http500, getCurrentExceptionMsg())
|
||||
|
||||
get "/api-tokens":
|
||||
checkAuth()
|
||||
|
||||
resp(Http200, $(%ctx.db.getApiTokensByUserId($session.user.id)))
|
||||
resp(Http200, $(%ctx.db.findApiTokensByUserId($session.user.id)))
|
||||
|
||||
post "/api-tokens":
|
||||
checkAuth()
|
||||
|
||||
var newToken: ApiToken
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
newToken = ApiToken(
|
||||
var newToken = ApiToken(
|
||||
userId: session.user.id,
|
||||
name: jsonBody["name"].getStr,
|
||||
name: jsonBody.getOrFail("name").getStr,
|
||||
expires: none[DateTime](),
|
||||
hashedToken: "")
|
||||
except: jsonResp(Http400)
|
||||
|
||||
try:
|
||||
let tokenValue = randomString(ctx.cfg.pwdCost)
|
||||
newToken.hashedToken = hashPwd(tokenValue, session.user.salt)
|
||||
let hashed = hashWithSalt(tokenValue, session.user.salt)
|
||||
|
||||
newToken.hashedToken = hashed.hash
|
||||
newToken = ctx.db.createApiToken(newToken)
|
||||
|
||||
let respToken = %newToken
|
||||
respToken["value"] = %tokenValue
|
||||
resp($respToken, JSON)
|
||||
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except AuthError: jsonResp(Http401, getCurrentExceptionMsg())
|
||||
except:
|
||||
debug getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
@ -237,6 +328,148 @@ proc start*(ctx: PMApiContext): void =
|
||||
else: jsonResp(Http500)
|
||||
except: jsonResp(Http404)
|
||||
|
||||
get "/measures":
|
||||
checkAuth()
|
||||
|
||||
try: resp($(%ctx.db.findMeasuresByUserId($session.user.id)), JSON)
|
||||
except:
|
||||
error "unable to retrieve measures for user:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
post "/measures":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
|
||||
if not (jsonBody.hasKey("slug") or jsonBody.hasKey("name")):
|
||||
raiseEx BadRequestError, "body must contain either the 'slug' field (short name), or the 'name' field, or both"
|
||||
|
||||
let slug =
|
||||
if jsonBody.hasKey("slug"): jsonBody["slug"].getStr.nameToSlug
|
||||
else: jsonBody["slug"].getStr.nameToSlug
|
||||
|
||||
let name =
|
||||
if jsonBody.hasKey("name"): jsonBody["name"].getStr
|
||||
else: jsonBody["slug"].getStr.capitalize
|
||||
|
||||
var newMeasure = Measure(
|
||||
userId: session.user.id,
|
||||
slug: slug,
|
||||
name: name,
|
||||
description: jsonBody.getIfExists("description").getStr(""),
|
||||
domainSource:
|
||||
if jsonBody.hasKey("domainSource"): some(jsonBody["domainSource"].getStr)
|
||||
else: none[string](),
|
||||
domainUnits: jsonBody.getIfExists("domainUnits").getStr(""),
|
||||
rangeSource:
|
||||
if jsonBody.hasKey("rangeSource"): some(jsonBody["rangeSource"].getStr)
|
||||
else: none[string](),
|
||||
rangeUnits: jsonBody.getIfExists("rangeUnits").getStr(""),
|
||||
analysis: @[])
|
||||
|
||||
if jsonBody.hasKey("analysis"):
|
||||
for a in jsonBody["analysis"].getElems:
|
||||
newMeasure.analysis.add(a.getStr)
|
||||
|
||||
resp($(%ctx.db.createMeasure(newMeasure)), JSON)
|
||||
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "unable to create new measure:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
get "/measures/@slug":
|
||||
checkAuth()
|
||||
|
||||
try: resp($(%ctx.getMeasureForSlug(session.user.id, @"slug")), JSON)
|
||||
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "unable to look up a measure by id:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
delete "/measures/@slug":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
|
||||
if ctx.db.deleteMeasure(measure): jsonResp(Http200)
|
||||
else: raiseEx ""
|
||||
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "unable to delete a measure:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
get "/measure/@slug":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
|
||||
resp($(%ctx.db.findMeasurementsByMeasureId($measure.id)), JSON)
|
||||
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "unable to list measurements:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
post "/measure/@slug":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
|
||||
let jsonBody = parseJson(request.body)
|
||||
|
||||
let newMeasurement = Measurement(
|
||||
measureId: measure.id,
|
||||
value: jsonBody.getOrFail("value").getInt,
|
||||
timestamp:
|
||||
if jsonBody.hasKey("timestamp"): jsonBody["timestamp"].getStr.parseIso8601.utc
|
||||
else: getTime().utc,
|
||||
extData:
|
||||
if jsonBody.hasKey("extData"): jsonBody["extData"]
|
||||
else: newJObject())
|
||||
|
||||
resp($(%ctx.db.createMeasurement(newMeasurement)), JSON)
|
||||
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "unable to add measurement:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
get "/measure/@slug/@id":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
|
||||
resp($(%ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id"))), JSON)
|
||||
|
||||
except ValueError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "unable to retrieve measurement:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
delete "/measure/@slug/@id":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let measure = ctx.getMeasureForSlug(session.user.id, @"slug")
|
||||
let measurement = ctx.getMeasurementForMeasure(measure.id, parseUUID(@"id"))
|
||||
if ctx.db.deleteMeasurement(measurement): jsonResp(Http200)
|
||||
else: raiseEx ""
|
||||
|
||||
except ValueError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except JsonParsingError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except NotFoundError: jsonResp(Http404, getCurrentExceptionMsg())
|
||||
except:
|
||||
error "unable to delete measurement:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
post "/service/debug/stop":
|
||||
if not ctx.cfg.debug: jsonResp(Http404)
|
||||
else:
|
||||
|
@ -14,6 +14,9 @@ type
|
||||
cfg*: PMApiConfig
|
||||
db*: PMApiDb
|
||||
|
||||
BadRequestError* = object of CatchableError
|
||||
AuthError* = object of CatchableError
|
||||
|
||||
proc `%`*(cfg: PMApiConfig): JsonNode =
|
||||
result = %* {
|
||||
"authSecret": cfg.authSecret,
|
||||
@ -22,6 +25,10 @@ proc `%`*(cfg: PMApiConfig): JsonNode =
|
||||
"port": cfg.port,
|
||||
"pwdCost": cfg.pwdCost }
|
||||
|
||||
template raiseEx*(errorType: type, reason: string): void =
|
||||
raise newException(errorType, reason)
|
||||
|
||||
proc raiseEx*(reason: string): void =
|
||||
raise newException(Exception, reason)
|
||||
|
||||
|
||||
|
@ -7,6 +7,7 @@ type
|
||||
email*: string
|
||||
hashedPwd*: string
|
||||
salt*: string
|
||||
isAdmin*: bool
|
||||
|
||||
ApiToken* = object
|
||||
id*: UUID
|
||||
@ -32,7 +33,7 @@ type
|
||||
measureId*: UUID
|
||||
value*: int
|
||||
timestamp*: DateTime
|
||||
extData*: string
|
||||
extData*: JsonNode
|
||||
|
||||
proc `$`*(u: User): string =
|
||||
return "User " & ($u.id)[0..6] & " - " & u.displayName & " <" & u.email & ">"
|
||||
@ -52,7 +53,8 @@ proc `%`*(u: User): JsonNode =
|
||||
result = %*{
|
||||
"id": $u.id,
|
||||
"email": u.email,
|
||||
"displayName": u.displayName
|
||||
"displayName": u.displayName,
|
||||
"isAdmin": u.isAdmin
|
||||
}
|
||||
|
||||
proc `%`*(tok: ApiToken): JsonNode =
|
||||
@ -85,6 +87,6 @@ proc `%`*(v: Measurement): JsonNode =
|
||||
"id": $v.id,
|
||||
"measureId": $v.measureId,
|
||||
"value": v.value,
|
||||
"timestampe": v.timestamp.formatIso8601,
|
||||
"timestamp": v.timestamp.formatIso8601,
|
||||
"extData": v.extData
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import bcrypt
|
||||
import bcrypt, nre, strutils, uuids
|
||||
|
||||
import ./configuration
|
||||
import ./db
|
||||
@ -6,14 +6,27 @@ import ./models
|
||||
|
||||
proc randomString*(cost: int8): string = genSalt(cost)
|
||||
|
||||
proc hashPwd*(pwd: string, salt: string): string =
|
||||
result = hash(pwd, salt)
|
||||
proc hashWithSalt*(pwd: string, salt: string): tuple[hash, salt: string] =
|
||||
result = (hash: hash(pwd, salt), salt: salt)
|
||||
|
||||
proc hashPwd*(pwd: string, cost: int8): string =
|
||||
let salt = genSalt(cost)
|
||||
result = hash(pwd, salt)
|
||||
proc hashPwd*(pwd: string, cost: int8): tuple[pwd, salt:string] =
|
||||
let hashed = hashWithSalt(pwd, genSalt(cost))
|
||||
result = (pwd: hashed.hash, salt: hashed.salt)
|
||||
|
||||
proc validatePwd*(u: User, givenPwd: string): bool =
|
||||
let salt = u.salt
|
||||
result = compare(u.hashedPwd, hash(givenPwd, salt))
|
||||
|
||||
proc nameToSlug*(name: string): string =
|
||||
result = name.replace(re"\W+", "-").toLower
|
||||
|
||||
proc getMeasureForSlug*(ctx: PMApiContext, userId: UUID, slug: string): Measure =
|
||||
let measures = ctx.db.findMeasuresByUserIdAndSlug($userId, slug)
|
||||
if measures.len < 1: raiseEx NotFoundError, "no measure named '" & slug & "'"
|
||||
result = measures[0]
|
||||
|
||||
proc getMeasurementForMeasure*(ctx: PMApiContext, measureId, measurementId: UUID): Measurement =
|
||||
let measurements = ctx.db.findMeasurementsByMeasureIdAndId($measureId, $measurementId)
|
||||
if measurements.len < 1: raiseEx NotFoundError, "no measurement for is '" & $measurementId & "'"
|
||||
result = measurements[0]
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
-- DOWN script for initial-schema (20190214122514)
|
||||
drop table if exists "values";
|
||||
drop table if exists "measurements";
|
||||
drop table if exists "measures";
|
||||
drop table if exists "api_tokens";
|
||||
drop table if exists "users";
|
||||
|
@ -6,7 +6,8 @@ create table "users" (
|
||||
display_name varchar not null,
|
||||
email varchar not null unique,
|
||||
hashed_pwd varchar not null,
|
||||
salt varchar not null
|
||||
salt varchar not null,
|
||||
is_admin boolean not null default false
|
||||
);
|
||||
|
||||
create table "api_tokens" (
|
||||
@ -27,13 +28,14 @@ create table "measures" (
|
||||
domain_units varchar not null default '',
|
||||
range_source varchar default null,
|
||||
range_units varchar not null default '',
|
||||
analysis varchar[] not null default '{}'
|
||||
analysis varchar[] not null default '{}',
|
||||
unique(user_id, slug)
|
||||
);
|
||||
|
||||
create table "values" (
|
||||
create table "measurements" (
|
||||
id uuid default uuid_generate_v4() primary key,
|
||||
measure_id uuid not null references measures (id) on delete cascade on update cascade,
|
||||
value integer not null,
|
||||
"timestamp" timestamp not null default current_timestamp,
|
||||
"timestamp" timestamp with time zone not null default current_timestamp,
|
||||
ext_data jsonb not null default '{}'::json
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user