WIP Refactor API into multiple sub-modules.
This commit is contained in:
parent
7bba4a0ad7
commit
1337d17105
7
api/personal_measure.config.json
Normal file
7
api/personal_measure.config.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"authSecret":"change me",
|
||||||
|
"dbConnString":"host=localhost port=5500 dbname=personal_measure user=postgres password=password",
|
||||||
|
"debug":true,
|
||||||
|
"port":8080,
|
||||||
|
"pwdCost":11
|
||||||
|
}
|
@ -8,9 +8,11 @@ description = "JDB\'s Personal Measures API"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
srcDir = "src/main/nim"
|
srcDir = "src/main/nim"
|
||||||
bin = @["personal_measure_api"]
|
bin = @["personal_measure_api"]
|
||||||
|
skipExt = @["nim"]
|
||||||
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires @["nim >= 0.19.4", "bcrypt", "docopt >= 0.6.8", "isaac >= 0.1.3", "jester >= 0.4.1", "jwt", "tempfile",
|
requires @["nim >= 0.19.4", "bcrypt", "cliutils >= 0.6.3", "docopt >= 0.6.8",
|
||||||
"timeutils >= 0.4.0", "uuids >= 0.1.10" ]
|
"isaac >= 0.1.3", "jester >= 0.4.1", "jwt", "tempfile", "timeutils >= 0.4.0",
|
||||||
|
"uuids >= 0.1.10" ]
|
||||||
|
@ -1,192 +1,83 @@
|
|||||||
import asyncdispatch, bcrypt, docopt, jester, json, jwt, options, times, timeutils
|
import cliutils, docopt, logging, jester, json, os, strutils, tables
|
||||||
|
|
||||||
import personal_measure_apipkg/models
|
import personal_measure_apipkg/configuration
|
||||||
import personal_measure_apipkg/db
|
|
||||||
import personal_measure_apipkg/version
|
import personal_measure_apipkg/version
|
||||||
|
import personal_measure_apipkg/api
|
||||||
|
|
||||||
const JSON = "application/json"
|
const DEFAULT_CONFIG = PMApiConfig(
|
||||||
|
authSecret: "change me",
|
||||||
|
dbConnString: "",
|
||||||
|
debug: false,
|
||||||
|
port: 8080,
|
||||||
|
pwdCost: 11)
|
||||||
|
|
||||||
type
|
proc loadConfig*(args: Table[string, docopt.Value] = initTable[string, docopt.Value]()): PMApiConfig =
|
||||||
PersonalMeasureApiConfig = object
|
|
||||||
authSecret*: string
|
|
||||||
dbConnString*: string
|
|
||||||
debug*: bool
|
|
||||||
port*: int
|
|
||||||
pwdCost*: int
|
|
||||||
|
|
||||||
Session = object
|
let filePath =
|
||||||
user*: User
|
if args["--config"]: $args["--config"]
|
||||||
issuedAt*, expires*: Time
|
else: "personal_measure.config.json"
|
||||||
|
|
||||||
proc newSession*(user: User): Session =
|
var json: JsonNode
|
||||||
result = Session(
|
try: json = parseFile(filePath)
|
||||||
user: user,
|
|
||||||
issuedAt: getTime().utc.trimNanoSec.toTime,
|
|
||||||
expires: daysForward(1).trimNanoSec.toTime)
|
|
||||||
|
|
||||||
proc raiseEx*(reason: string): void =
|
|
||||||
raise newException(Exception, reason)
|
|
||||||
|
|
||||||
|
|
||||||
template halt(code: HttpCode,
|
|
||||||
headers: RawHeaders,
|
|
||||||
content: string): typed =
|
|
||||||
## Immediately replies with the specified request. This means any further
|
|
||||||
## code will not be executed after calling this template in the current
|
|
||||||
## route.
|
|
||||||
bind TCActionSend, newHttpHeaders
|
|
||||||
result[0] = CallbackAction.TCActionSend
|
|
||||||
result[1] = code
|
|
||||||
result[2] = some(headers)
|
|
||||||
result[3] = content
|
|
||||||
result.matched = true
|
|
||||||
break allRoutes
|
|
||||||
|
|
||||||
template jsonResp(code: HttpCode, details: string = "", headers: RawHeaders = @{:} ) =
|
|
||||||
halt(
|
|
||||||
code,
|
|
||||||
headers & @{"Content-Type": JSON},
|
|
||||||
$(%* {
|
|
||||||
"statusCode": code.int,
|
|
||||||
"status": $code,
|
|
||||||
"details": details
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
template json500Resp(ex: ref Exception, details: string = ""): void =
|
|
||||||
when not defined(release): debug ex.getStackTrace()
|
|
||||||
error details & ":\n" & ex.msg
|
|
||||||
jsonResp(Http500)
|
|
||||||
|
|
||||||
proc toJWT*(cfg: PersonalMeasureApiConfig, session: Session): string =
|
|
||||||
## Make a JST token for this session.
|
|
||||||
var jwt = JWT(
|
|
||||||
header: JOSEHeader(alg: HS256, typ: "jwt"),
|
|
||||||
claims: toClaims(%*{
|
|
||||||
"sub": $(session.user.id),
|
|
||||||
"iat": session.issuedAt.toUnix.int,
|
|
||||||
"exp": session.expires.toUnix.int }))
|
|
||||||
|
|
||||||
jwt.sign(cfg.authSecret)
|
|
||||||
result = $jwt
|
|
||||||
|
|
||||||
proc fromJWT*(cfg: PersonalMeasureApiConfig, strTok: string): Session =
|
|
||||||
## Validate a given JWT and extract the session data.
|
|
||||||
let jwt = toJWT(strTok)
|
|
||||||
var secret = cfg.authSecret
|
|
||||||
if not jwt.verify(secret): raiseEx "Unable to verify auth token."
|
|
||||||
jwt.verifyTimeClaims()
|
|
||||||
|
|
||||||
# Find the user record (if authenticated)
|
|
||||||
let userId = jwt.claims["sub"].node.str
|
|
||||||
let user = db.getUser(userId)
|
|
||||||
if users.len != 1: raiseEx "Could not find session user."
|
|
||||||
|
|
||||||
result = Session(
|
|
||||||
user: user,
|
|
||||||
issuedAt: fromUnix(jwt.claims["iat"].node.num),
|
|
||||||
expires: fromUnix(jwt.claims["exp"].node.num))
|
|
||||||
|
|
||||||
proc extractSession(cfg: PersonalMeasureApiConfig, request: Request): Session =
|
|
||||||
## Helper to extract a session from a reqest.
|
|
||||||
|
|
||||||
# Find the auth header
|
|
||||||
if not request.headers.hasKey("Authorization"):
|
|
||||||
raiseEx "No auth token."
|
|
||||||
|
|
||||||
# Read and verify the JWT token
|
|
||||||
let headerVal = request.headers["Authorization"]
|
|
||||||
if not headerVal.startsWith("Bearer "):
|
|
||||||
raiseEx "Invalid Authentication type (only 'Bearer' is supported)."
|
|
||||||
|
|
||||||
result = fromJWT(cfg, headerVal[7..^1])
|
|
||||||
|
|
||||||
proc hashPwd*(pwd: string, cost: int8): string =
|
|
||||||
let salt = genSalt(cost)
|
|
||||||
result = hash(pwd, salt)
|
|
||||||
|
|
||||||
proc validatePwd*(u: ref User, givenPwd: string): bool =
|
|
||||||
let salt = u.hashedPwd[0..28] # TODO: magic numbers
|
|
||||||
result = compare(u.hashedPwd, hash(givenPwd, salt))
|
|
||||||
|
|
||||||
proc makeAuthToken*(cfg: PersonalMeasureApiConfig, uname, pwd: string): string =
|
|
||||||
## Given a username and pwd, validate the combination and generate a JWT
|
|
||||||
## token string.
|
|
||||||
|
|
||||||
if uname.len == 0 or pwd.len == 0:
|
|
||||||
raiseEx "fields 'username' and 'password' required"
|
|
||||||
|
|
||||||
# find the user record
|
|
||||||
let users = cfg.users.filterIt(it.name == uname)
|
|
||||||
if users.len != 1: raiseEx "invalid username or password"
|
|
||||||
|
|
||||||
let user = users[0]
|
|
||||||
|
|
||||||
if not validatePwd(user, pwd): raiseEx "invalid username or password"
|
|
||||||
|
|
||||||
let session = newSession(user)
|
|
||||||
|
|
||||||
result = toJWT(cfg, session)
|
|
||||||
|
|
||||||
proc makeApiKey*(cfg: PersonalMeasureApiConfig, uname: string): string =
|
|
||||||
## Given a username, make an API token (JWT token string that does not
|
|
||||||
## expire). Note that this does not validate the username/pwd combination. It
|
|
||||||
## is not intended to be exposed publicly via the API, but serve as a utility
|
|
||||||
## function for an administrator to setup a unsupervised account (git access
|
|
||||||
## for example).
|
|
||||||
|
|
||||||
if uname.len == 0: raiseEx "no username given"
|
|
||||||
|
|
||||||
# find the user record
|
|
||||||
let users = cfg.users.filterIt(it.name == uname)
|
|
||||||
if users.len != 1: raiseEx "invalid username"
|
|
||||||
|
|
||||||
let session = Session(
|
|
||||||
user: users[0],
|
|
||||||
issuedAt: getTime(),
|
|
||||||
expires: daysForward(365 * 1000).toTime())
|
|
||||||
|
|
||||||
result = toJWT(cfg, session);
|
|
||||||
|
|
||||||
template checkAuth() =
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
var session {.inject.}: Session
|
|
||||||
|
|
||||||
try: session = extractSession(cfg, request)
|
|
||||||
except:
|
except:
|
||||||
debug "Auth failed: " & getCurrentExceptionMsg()
|
json = %DEFAULT_CONFIG
|
||||||
jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"})
|
if not existsFile(filePath):
|
||||||
|
info "created new configuration file \"" & filePath & "\""
|
||||||
|
filePath.writeFile($json)
|
||||||
|
else:
|
||||||
|
warn "Cannot read configuration file \"" & filePath & "\":\n\t" &
|
||||||
|
getCurrentExceptionMsg()
|
||||||
|
|
||||||
|
let cfg = CombinedConfig(docopt: args, json: json)
|
||||||
|
|
||||||
proc start*(cfg: PersonalMeasureApiConfig): void =
|
result = PMApiConfig(
|
||||||
|
authSecret: cfg.getVal("authSecret"),
|
||||||
|
dbConnString: cfg.getVal("dbConnString"),
|
||||||
|
debug: "true".startsWith(cfg.getVal("debug", "false").toLower()),
|
||||||
|
port: parseInt(cfg.getVal("port", "8080")),
|
||||||
|
pwdCost: parseInt(cfg.getVal("pwdCost", "11")))
|
||||||
|
|
||||||
|
proc initContext(args: Table[string, docopt.Value]): PMApiContext =
|
||||||
|
|
||||||
var stopFuture = newFuture[void]()
|
var cfg: PMApiConfig
|
||||||
|
var db: PMApiDb
|
||||||
|
|
||||||
settings:
|
try: cfg = loadConfig(args)
|
||||||
port = Port(cfg.port)
|
except: raiseEx "Unable to load configuration: \n\t" & getCurrentExceptionMsg()
|
||||||
appName = "/api"
|
|
||||||
|
|
||||||
routes:
|
try: db = connect(cfg.dbConnString)
|
||||||
|
except: raiseEx "Unable to connect to the database:\n\t" & getCurrentExceptionMsg()
|
||||||
get "/version":
|
|
||||||
resp($(%("personal_measure_api v" & PM_API_VERSION)), JSON)
|
|
||||||
|
|
||||||
post "/service/debug/stop":
|
|
||||||
if not cfg.debug: jsonResp(Http404)
|
|
||||||
else:
|
|
||||||
let shutdownFut = sleepAsync(100)
|
|
||||||
shutdownFut.callback = proc(): void = complete(stopFuture)
|
|
||||||
resp($(%"shutting down"), JSON)
|
|
||||||
|
|
||||||
waitFor(stopFuture)
|
|
||||||
|
|
||||||
|
result = PMApiContext(cfg: cfg, db: db)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
start(PersonalMeasureApiConfig(
|
|
||||||
debug: true,
|
try:
|
||||||
port: 8090,
|
let doc = """
|
||||||
pwdCost: 11,
|
Usage:
|
||||||
dbConnString: "host=localhost port=5500 username=postgres password=password dbname=personal_measure"))
|
personal_measure_api test [options]
|
||||||
|
personal_measure_api serve [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-C, --config <cfgFile> Location of the config file (defaults to personal_measure.config.json)
|
||||||
|
"""
|
||||||
|
logging.addHandler(newConsoleLogger())
|
||||||
|
|
||||||
|
# Initialize our service context
|
||||||
|
let args = docopt(doc, version = PM_API_VERSION)
|
||||||
|
let ctx = initContext(args)
|
||||||
|
|
||||||
|
if args["test"]:
|
||||||
|
echo "Test"
|
||||||
|
|
||||||
|
if args["serve"]:
|
||||||
|
start(PMApiConfig(
|
||||||
|
debug: true,
|
||||||
|
port: 8090,
|
||||||
|
pwdCost: 11,
|
||||||
|
dbConnString: "host=localhost port=5500 username=postgres password=password dbname=personal_measure"))
|
||||||
|
except:
|
||||||
|
fatal "pit: " & getCurrentExceptionMsg()
|
||||||
|
#raise getCurrentException()
|
||||||
|
quit(QuitFailure)
|
||||||
|
146
api/src/main/nim/personal_measure_apipkg/api.nim
Normal file
146
api/src/main/nim/personal_measure_apipkg/api.nim
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import asyncdispatch, jester, json, jwt, strutils, times, timeutils, uuids
|
||||||
|
|
||||||
|
import ./db
|
||||||
|
import ./configuration
|
||||||
|
import ./models
|
||||||
|
import ./service
|
||||||
|
|
||||||
|
const JSON = "application/json"
|
||||||
|
|
||||||
|
type
|
||||||
|
Session* = object
|
||||||
|
user*: User
|
||||||
|
issuedAt*, expires*: Time
|
||||||
|
|
||||||
|
proc newSession*(user: User): Session =
|
||||||
|
result = Session(
|
||||||
|
user: user,
|
||||||
|
issuedAt: getTime().utc.trimNanoSec.toTime,
|
||||||
|
expires: daysForward(1).trimNanoSec.toTime)
|
||||||
|
|
||||||
|
template halt(code: HttpCode,
|
||||||
|
headers: RawHeaders,
|
||||||
|
content: string): typed =
|
||||||
|
## Immediately replies with the specified request. This means any further
|
||||||
|
## code will not be executed after calling this template in the current
|
||||||
|
## route.
|
||||||
|
bind TCActionSend, newHttpHeaders
|
||||||
|
result[0] = CallbackAction.TCActionSend
|
||||||
|
result[1] = code
|
||||||
|
result[2] = some(headers)
|
||||||
|
result[3] = content
|
||||||
|
result.matched = true
|
||||||
|
break allRoutes
|
||||||
|
|
||||||
|
template jsonResp(code: HttpCode, details: string = "", headers: RawHeaders = @{:} ) =
|
||||||
|
halt(
|
||||||
|
code,
|
||||||
|
headers & @{"Content-Type": JSON},
|
||||||
|
$(%* {
|
||||||
|
"statusCode": code.int,
|
||||||
|
"status": $code,
|
||||||
|
"details": details
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
template json500Resp(ex: ref Exception, details: string = ""): void =
|
||||||
|
when not defined(release): debug ex.getStackTrace()
|
||||||
|
error details & ":\n" & ex.msg
|
||||||
|
jsonResp(Http500)
|
||||||
|
|
||||||
|
proc toJWT*(ctx: PMApiContext, session: Session): string =
|
||||||
|
## Make a JST token for this session.
|
||||||
|
var jwt = JWT(
|
||||||
|
header: JOSEHeader(alg: HS256, typ: "jwt"),
|
||||||
|
claims: toClaims(%*{
|
||||||
|
"sub": $(session.user.id),
|
||||||
|
"iat": session.issuedAt.toUnix.int,
|
||||||
|
"exp": session.expires.toUnix.int }))
|
||||||
|
|
||||||
|
jwt.sign(ctx.cfg.authSecret)
|
||||||
|
result = $jwt
|
||||||
|
|
||||||
|
proc fromJWT*(ctx: PMApiContext, strTok: string): Session =
|
||||||
|
## Validate a given JWT and extract the session data.
|
||||||
|
let jwt = toJWT(strTok)
|
||||||
|
var secret = ctx.cfg.authSecret
|
||||||
|
if not jwt.verify(secret): raiseEx "Unable to verify auth token."
|
||||||
|
jwt.verifyTimeClaims()
|
||||||
|
|
||||||
|
# Find the user record (if authenticated)
|
||||||
|
let userId = jwt.claims["sub"].node.str
|
||||||
|
var user: User
|
||||||
|
try: user = ctx.db.getUser(parseUUID(userId))
|
||||||
|
except: raiseEx "unknown user"
|
||||||
|
|
||||||
|
result = Session(
|
||||||
|
user: user,
|
||||||
|
issuedAt: fromUnix(jwt.claims["iat"].node.num),
|
||||||
|
expires: fromUnix(jwt.claims["exp"].node.num))
|
||||||
|
|
||||||
|
proc extractSession(ctx: PMApiContext, request: Request): Session =
|
||||||
|
## Helper to extract a session from a reqest.
|
||||||
|
|
||||||
|
# Find the auth header
|
||||||
|
if not request.headers.hasKey("Authorization"):
|
||||||
|
raiseEx "No auth token."
|
||||||
|
|
||||||
|
# Read and verify the JWT token
|
||||||
|
let headerVal = request.headers["Authorization"]
|
||||||
|
if not headerVal.startsWith("Bearer "):
|
||||||
|
raiseEx "Invalid Authentication type (only 'Bearer' is supported)."
|
||||||
|
|
||||||
|
result = fromJWT(ctx, headerVal[7..^1])
|
||||||
|
|
||||||
|
proc makeAuthToken*(ctx: PMApiContext, email, pwd: string): string =
|
||||||
|
## Given a user's email and pwd, validate the combination and generate a JWT
|
||||||
|
## token string.
|
||||||
|
|
||||||
|
if email.len == 0 or pwd.len == 0:
|
||||||
|
raiseEx "fields 'username' and 'password' required"
|
||||||
|
|
||||||
|
# find the user record
|
||||||
|
var user: User
|
||||||
|
try: user = ctx.db.getUserByEmail(email)
|
||||||
|
except: raiseEx "invalid username or password"
|
||||||
|
|
||||||
|
if not validatePwd(user, pwd): raiseEx "invalid username or password"
|
||||||
|
|
||||||
|
let session = newSession(user)
|
||||||
|
|
||||||
|
result = toJWT(ctx, session)
|
||||||
|
|
||||||
|
template checkAuth() =
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
var session {.inject.}: Session
|
||||||
|
|
||||||
|
try: session = extractSession(cfg, request)
|
||||||
|
except:
|
||||||
|
debug "Auth failed: " & getCurrentExceptionMsg()
|
||||||
|
jsonResp(Http401, "Unauthorized", @{"WWW-Authenticate": "Bearer"})
|
||||||
|
|
||||||
|
proc start*(cfg: PMApiConfig): void =
|
||||||
|
|
||||||
|
var stopFuture = newFuture[void]()
|
||||||
|
|
||||||
|
settings:
|
||||||
|
port = Port(cfg.port)
|
||||||
|
appName = "/api"
|
||||||
|
|
||||||
|
routes:
|
||||||
|
|
||||||
|
get "/version":
|
||||||
|
resp($(%("personal_measure_api v" & PM_API_VERSION)), JSON)
|
||||||
|
|
||||||
|
post "/service/debug/stop":
|
||||||
|
if not cfg.debug: jsonResp(Http404)
|
||||||
|
else:
|
||||||
|
let shutdownFut = sleepAsync(100)
|
||||||
|
shutdownFut.callback = proc(): void = complete(stopFuture)
|
||||||
|
resp($(%"shutting down"), JSON)
|
||||||
|
|
||||||
|
waitFor(stopFuture)
|
||||||
|
|
27
api/src/main/nim/personal_measure_apipkg/configuration.nim
Normal file
27
api/src/main/nim/personal_measure_apipkg/configuration.nim
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import ./db
|
||||||
|
|
||||||
|
type
|
||||||
|
PMApiConfig* = object
|
||||||
|
authSecret*: string
|
||||||
|
dbConnString*: string
|
||||||
|
debug*: bool
|
||||||
|
port*: int
|
||||||
|
pwdCost*: int
|
||||||
|
|
||||||
|
PMApiContext* = object
|
||||||
|
cfg*: PMApiConfig
|
||||||
|
db*: PMApiDb
|
||||||
|
|
||||||
|
proc `%`*(cfg: PMApiConfig): JsonNode =
|
||||||
|
result = %* {
|
||||||
|
"authSecret": cfg.authSecret,
|
||||||
|
"dbConnString": cfg.dbConnString,
|
||||||
|
"debug": cfg.debug,
|
||||||
|
"port": cfg.port,
|
||||||
|
"pwdCost": cfg.pwdCost }
|
||||||
|
|
||||||
|
proc raiseEx*(reason: string): void =
|
||||||
|
raise newException(Exception, reason)
|
||||||
|
|
@ -4,14 +4,32 @@ import db_postgres, macros, options, postgres, sequtils, strutils, times,
|
|||||||
import ./models
|
import ./models
|
||||||
import ./db_common
|
import ./db_common
|
||||||
|
|
||||||
# proc create: Typed create methods for specific records
|
type
|
||||||
proc createUser*(db: DbConn, user: User): User = return db.createRecord(user)
|
PMApiDb* = ref object
|
||||||
proc createApiToken*(db: DbConn, token: ApiToken): ApiToken = return db.createRecord(token)
|
conn: DbConn
|
||||||
proc createMeasure*(db: DbConn, measure: Measure): Measure = return db.createRecord(measure)
|
|
||||||
proc createValue*(db: DbConn, value: Value): Value = return db.createRecord(value)
|
|
||||||
|
|
||||||
proc getUser*(db: DbConn, id: UUID): User = return db.getRecord(User, id)
|
|
||||||
proc getUser*(db: DbConn, id: string): User = return db.getRecord(User, parseUUID(id))
|
proc connect*(connString: string): PMApiDb =
|
||||||
proc getApiToken*(db: DbConn, id: UUID): ApiToken = return db.getRecord(ApiToken, id)
|
result = PMApiDb(conn: open("", "", "", connString))
|
||||||
proc getMeasure*(db: DbConn, id: UUID): Measure = return db.getRecord(Measure, id)
|
|
||||||
proc getValue*(db: DbConn, id: UUID): Value = return db.getRecord(Value, id)
|
macro makeGetRecord(modelType: type): untyped =
|
||||||
|
echo modelType.getType.treeRepr
|
||||||
|
#[
|
||||||
|
let procIdent = ident("get" & $modelType.getType[1])
|
||||||
|
return quote do:
|
||||||
|
proc `procIdent`*(db: PMApiDb, id: UUID): `modelType` = return db.conn.getRecord(`modelType`, id)
|
||||||
|
]#
|
||||||
|
|
||||||
|
proc createUser*(db: PMApiDb, user: User): User = return db.conn.createRecord(user)
|
||||||
|
proc createApiToken*(db: PMApiDb, token: ApiToken): ApiToken = return db.conn.createRecord(token)
|
||||||
|
proc createMeasure*(db: PMApiDb, measure: Measure): Measure = return db.conn.createRecord(measure)
|
||||||
|
proc createValue*(db: PMApiDb, value: Value): Value = return db.conn.createRecord(value)
|
||||||
|
|
||||||
|
proc getUser*(db: PMApiDb, id: UUID): User = return db.conn.getRecord(User, id)
|
||||||
|
proc getUser*(db: PMApiDb, id: string): User = return db.conn.getRecord(User, parseUUID(id))
|
||||||
|
proc getApiToken*(db: PMApiDb, id: UUID): ApiToken = return db.conn.getRecord(ApiToken, id)
|
||||||
|
proc getMeasure*(db: PMApiDb, id: UUID): Measure = return db.conn.getRecord(Measure, id)
|
||||||
|
proc getValue*(db: PMApiDb, id: UUID): Value = return db.conn.getRecord(Value, id)
|
||||||
|
|
||||||
|
#proc getUsersWhere*(db: PMApiDb, whereClause: string, values: varargs[string, dbFormat]): User =
|
||||||
|
# return db.conn.getRecordsWhere(User, whereClause, values)
|
||||||
|
@ -40,8 +40,19 @@ template getRecord*(db: DbConn, modelType: type, id: UUID): untyped =
|
|||||||
"SELECT " & columnNamesForModel(modelType).join(",") &
|
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||||
" FROM " & tableName(modelType) &
|
" FROM " & tableName(modelType) &
|
||||||
" WHERE id = ?"), @[$id])
|
" WHERE id = ?"), @[$id])
|
||||||
|
|
||||||
|
if row.allIt(it.len == 0):
|
||||||
|
raise newException(KeyError, "no record for id " & $id)
|
||||||
|
|
||||||
rowToModel(modelType, row)
|
rowToModel(modelType, row)
|
||||||
|
|
||||||
|
template getRecordsWhere*(db: DbConn, modelType: type, whereClause: string, values: varargs[string, dbFormat]): untyped =
|
||||||
|
db.getAllRows(sql(
|
||||||
|
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||||
|
" FROM " & tableName(modelType) &
|
||||||
|
" WHERE " & whereClause), values)
|
||||||
|
.mapIt(rowToModel(modelType, it))
|
||||||
|
|
||||||
template getAllRecords*(db: DbConn, modelType: type): untyped =
|
template getAllRecords*(db: DbConn, modelType: type): untyped =
|
||||||
db.getAllRows(sql(
|
db.getAllRows(sql(
|
||||||
"SELECT " & columnNamesForModel(modelType).join(",") &
|
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||||
|
14
api/src/main/nim/personal_measure_apipkg/service.nim
Normal file
14
api/src/main/nim/personal_measure_apipkg/service.nim
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import bcrypt
|
||||||
|
|
||||||
|
import ./configuration
|
||||||
|
import ./db
|
||||||
|
import ./models
|
||||||
|
|
||||||
|
proc hashPwd*(pwd: string, cost: int8): string =
|
||||||
|
let salt = genSalt(cost)
|
||||||
|
result = hash(pwd, salt)
|
||||||
|
|
||||||
|
proc validatePwd*(u: User, givenPwd: string): bool =
|
||||||
|
let salt = u.hashedPwd[0..28] # TODO: magic numbers
|
||||||
|
result = compare(u.hashedPwd, hash(givenPwd, salt))
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user