WIP Logging service and API.
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
PGSQL_CONTAINER_ID=`cat postgres.container.id`
|
||||
DB_NAME="personal_measure"
|
||||
SOURCES=$(wildcard src/main/nim/*.nim) $(wildcard src/main/nim/personal_measure_apipkg/*.nim)
|
||||
|
||||
start-db: start-postgres
|
||||
serve: personal_measure_api start-postgres
|
||||
./personal_measure_api serve
|
||||
|
||||
postgres.container.id:
|
||||
docker run --name postgres-$(DB_NAME) -e POSTGRES_PASSWORD=password -p 5500:5432 -d postgres > postgres.container.id
|
||||
@ -24,3 +26,6 @@ delete-postgres-container:
|
||||
|
||||
connect:
|
||||
PGPASSWORD=password psql -p 5500 -U postgres -h localhost ${DB_NAME}
|
||||
|
||||
personal_measure_api: $(SOURCES)
|
||||
nimble build
|
||||
|
@ -153,7 +153,9 @@ proc makeAuthToken*(ctx: PMApiContext, email, pwd: string): string =
|
||||
let users = ctx.db.findUsersByEmail(email)
|
||||
if users.len != 1: raiseEx ""
|
||||
user = users[0]
|
||||
except: raiseEx AuthError, "invalid username or password"
|
||||
except:
|
||||
error "unable to find user", getCurrentExceptionMsg()
|
||||
raiseEx AuthError, "invalid username or password"
|
||||
|
||||
if not validatePwd(user, pwd): raiseEx AuthError, "invalid username or password"
|
||||
|
||||
@ -510,6 +512,41 @@ proc start*(ctx: PMApiContext): void =
|
||||
error "unable to delete measurement:\n\t" & getCurrentExceptionMsg()
|
||||
jsonResp(Http500)
|
||||
|
||||
post "/log":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let jsonBody = parseJson(request.body)
|
||||
let logEntry = ClientLogEntry(
|
||||
userId: session.user.id,
|
||||
level: jsonBody.getOrFail("level").getStr,
|
||||
message: jsonBody.getOrFail("message").getStr,
|
||||
scope: jsonBody.getOrFail("scope").getStr,
|
||||
stacktrace: jsonBody.getIfExists("stacktrace").getStr(""),
|
||||
timestamp: jsonBody.getOrFail("timestampe").getStr.parseIso8601
|
||||
)
|
||||
resp(Http200, $(%ctx.db.createClientLogEntry(logEntry)), JSON)
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except: jsonResp(Http500, getCurrentExceptionMsg())
|
||||
|
||||
post "/log/batch":
|
||||
checkAuth()
|
||||
|
||||
try:
|
||||
let jsonBody = parseJson(request.body);
|
||||
let respMsgs = jsonBody.getElems.mapIt(
|
||||
ClientLogEntry(
|
||||
userId: session.user.id,
|
||||
level: it.getOrFail("level").getStr,
|
||||
message: it.getOrFail("message").getStr,
|
||||
scope: it.getOrFail("scope").getStr,
|
||||
stacktrace: it.getIfExists("stacktrace").getStr(""),
|
||||
timestamp: it.getOrFail("timestampe").getStr.parseIso8601
|
||||
))
|
||||
resp(Http200, $(%respMsgs), JSON)
|
||||
except BadRequestError: jsonResp(Http400, getCurrentExceptionMsg())
|
||||
except: jsonResp(Http500, getCurrentExceptionMsg())
|
||||
|
||||
post "/service/debug/stop":
|
||||
if not ctx.cfg.debug: jsonResp(Http404)
|
||||
else:
|
||||
|
@ -14,7 +14,7 @@ type
|
||||
proc connect*(connString: string): PMApiDb =
|
||||
result = PMApiDb(conn: open("", "", "", connString))
|
||||
|
||||
generateProcsForModels([User, ApiToken, Measure, Measurement])
|
||||
generateProcsForModels([User, ApiToken, Measure, Measurement, ClientLogEntry])
|
||||
|
||||
generateLookup(User, @["email"])
|
||||
|
||||
@ -27,3 +27,5 @@ generateLookup(Measure, @["userId", "slug"])
|
||||
|
||||
generateLookup(Measurement, @["measureId"])
|
||||
generateLookup(Measurement, @["measureId", "id"])
|
||||
|
||||
generateLookup(ClientLogEntry, @["userId"])
|
||||
|
@ -18,14 +18,13 @@ proc createRecord*[T](db: DbConn, rec: T): T =
|
||||
|
||||
# Confusingly, getRow allows inserts and updates. We use it to get back the ID
|
||||
# we want from the row.
|
||||
let newIdStr = db.getValue(sql(
|
||||
let newRow = db.getRow(sql(
|
||||
"INSERT INTO " & tableName(rec) &
|
||||
" (" & mc.columns.join(",") & ") " &
|
||||
" VALUES (" & mc.placeholders.join(",") & ") " &
|
||||
" RETURNING id"), mc.values)
|
||||
" RETURNING *"), mc.values)
|
||||
|
||||
result = rec
|
||||
result.id = parseUUID(newIdStr)
|
||||
result = rowToModel(T, newRow)
|
||||
|
||||
proc updateRecord*[T](db: DbConn, rec: T): bool =
|
||||
var mc = newMutateClauses()
|
||||
@ -39,13 +38,13 @@ proc updateRecord*[T](db: DbConn, rec: T): bool =
|
||||
|
||||
return numRowsUpdated > 0;
|
||||
|
||||
template deleteRecord*(db: DbConn, modelType: type, id: UUID): untyped =
|
||||
template deleteRecord*(db: DbConn, modelType: type, id: typed): untyped =
|
||||
db.tryExec(sql("DELETE FROM " & tableName(modelType) & " WHERE id = ?"), $id)
|
||||
|
||||
proc deleteRecord*[T](db: DbConn, rec: T): bool =
|
||||
return db.tryExec(sql("DELETE FROM " & tableName(rec) & " WHERE id = ?"), $rec.id)
|
||||
|
||||
template getRecord*(db: DbConn, modelType: type, id: UUID): untyped =
|
||||
template getRecord*(db: DbConn, modelType: type, id: typed): untyped =
|
||||
let row = db.getRow(sql(
|
||||
"SELECT " & columnNamesForModel(modelType).join(",") &
|
||||
" FROM " & tableName(modelType) &
|
||||
@ -88,15 +87,16 @@ macro generateProcsForModels*(modelTypes: openarray[type]): untyped =
|
||||
let createName = ident("create" & modelName)
|
||||
let updateName = ident("update" & modelName)
|
||||
let deleteName = ident("delete" & modelName)
|
||||
let idType = typeOfColumn(t, "id")
|
||||
result.add quote do:
|
||||
proc `getName`*(db: PMApiDb, id: UUID): `t` = getRecord(db.conn, `t`, id)
|
||||
proc `getName`*(db: PMApiDb, id: `idType`): `t` = getRecord(db.conn, `t`, id)
|
||||
proc `getAllName`*(db: PMApiDb): seq[`t`] = getAllRecords(db.conn, `t`)
|
||||
proc `findWhereName`*(db: PMApiDb, whereClause: string, values: varargs[string, dbFormat]): seq[`t`] =
|
||||
return findRecordsWhere(db.conn, `t`, whereClause, values)
|
||||
proc `createName`*(db: PMApiDb, rec: `t`): `t` = createRecord(db.conn, rec)
|
||||
proc `updateName`*(db: PMApiDb, rec: `t`): bool = updateRecord(db.conn, rec)
|
||||
proc `deleteName`*(db: PMApiDb, rec: `t`): bool = deleteRecord(db.conn, rec)
|
||||
proc `deleteName`*(db: PMApiDb, id: UUID): bool = deleteRecord(db.conn, `t`, id)
|
||||
proc `deleteName`*(db: PMApiDb, id: `idType`): bool = deleteRecord(db.conn, `t`, id)
|
||||
|
||||
macro generateLookup*(modelType: type, fields: seq[string]): untyped =
|
||||
let fieldNames = fields[1].mapIt($it)
|
||||
|
@ -10,6 +10,13 @@ type
|
||||
placeholders*: seq[string]
|
||||
values*: seq[string]
|
||||
|
||||
# TODO: more complete implementation
|
||||
# see https://github.com/blakeembrey/pluralize
|
||||
proc pluralize(name: string): string =
|
||||
if name[^2..^1] == "ey": return name[0..^3] & "ies"
|
||||
if name[^1] == 'y': return name[0..^2] & "ies"
|
||||
return name & "s"
|
||||
|
||||
macro modelName*(model: object): string =
|
||||
return $model.getTypeInst
|
||||
|
||||
@ -38,10 +45,10 @@ proc dbNameToIdent*(name: string): string =
|
||||
return @[parts[0]].concat(parts[1..^1].mapIt(capitalize(it))).join("")
|
||||
|
||||
proc tableName*(modelType: type): string =
|
||||
return modelName(modelType).identNameToDb & "s"
|
||||
return pluralize(modelName(modelType).identNameToDb)
|
||||
|
||||
proc tableName*[T](rec: T): string =
|
||||
return modelName(rec).identNameToDb & "s"
|
||||
return pluralize(modelName(rec).identNameToDb)
|
||||
|
||||
proc dbFormat*(s: string): string = return s
|
||||
|
||||
@ -131,8 +138,7 @@ proc createParseStmt*(t, value: NimNode): NimNode =
|
||||
if `value`.len == 0: none[`innerType`]()
|
||||
else: some(`parseStmt`)
|
||||
|
||||
else:
|
||||
error "Unknown value object type: " & $t.getTypeInst
|
||||
else: error "Unknown value object type: " & $t.getTypeInst
|
||||
|
||||
elif t.typeKind == ntyRef:
|
||||
|
||||
@ -211,6 +217,24 @@ macro listFields*(t: typed): untyped =
|
||||
|
||||
result = newLit(fields)
|
||||
|
||||
proc typeOfColumn*(modelType: NimNode, colName: string): NimNode =
|
||||
modelType.walkFieldDefs:
|
||||
if $fieldIdent != colName: continue
|
||||
|
||||
if fieldType.typeKind == ntyObject:
|
||||
|
||||
if fieldType.getType == UUID.getType: return ident("UUID")
|
||||
elif fieldType.getType == DateTime.getType: return ident("DateTime")
|
||||
elif fieldType.getType == Option.getType: return ident("Option")
|
||||
else: error "Unknown column type: " & $fieldType.getTypeInst
|
||||
|
||||
else: return fieldType
|
||||
|
||||
raise newException(Exception,
|
||||
"model of type '" & $modelType & "' has no column named '" & colName & "'")
|
||||
|
||||
proc isZero(val: int): bool = return val == 0
|
||||
|
||||
macro populateMutateClauses*(t: typed, newRecord: bool, mc: var MutateClauses): untyped =
|
||||
|
||||
result = newStmtList()
|
||||
@ -249,6 +273,3 @@ macro populateMutateClauses*(t: typed, newRecord: bool, mc: var MutateClauses):
|
||||
`mc`.columns.add(identNameToDb(`fieldName`))
|
||||
`mc`.placeholders.add("?")
|
||||
`mc`.values.add(dbFormat(`t`.`fieldIdent`))
|
||||
|
||||
#echo result.repr
|
||||
|
||||
|
@ -35,6 +35,15 @@ type
|
||||
timestamp*: DateTime
|
||||
extData*: JsonNode
|
||||
|
||||
ClientLogEntry* = object
|
||||
id*: int
|
||||
userId*: UUID
|
||||
level*: string
|
||||
message*: string
|
||||
scope*: string
|
||||
stacktrace*: string
|
||||
timestamp*: DateTime
|
||||
|
||||
proc `$`*(u: User): string =
|
||||
return "User " & ($u.id)[0..6] & " - " & u.displayName & " <" & u.email & ">"
|
||||
|
||||
@ -49,6 +58,10 @@ proc `$`*(m: Measure): string =
|
||||
proc `$`*(v: Measurement): string =
|
||||
return "Measurement " & ($v.id)[0..6] & " - " & ($v.measureId)[0..6] & " = " & $v.value
|
||||
|
||||
proc `%`*(uuid: UUID): JsonNode = %($uuid)
|
||||
|
||||
proc `%`*(dt: DateTime): JsonNode = %(dt.formatIso8601)
|
||||
|
||||
proc `%`*(u: User): JsonNode =
|
||||
result = %*{
|
||||
"id": $u.id,
|
||||
@ -81,12 +94,3 @@ proc `%`*(m: Measure): JsonNode =
|
||||
|
||||
if m.domainSource.isSome: result["domainSource"] = %(m.domainSource.get)
|
||||
if m.rangeSource.isSome: result["rangeSource"] = %(m.rangeSource.get)
|
||||
|
||||
proc `%`*(v: Measurement): JsonNode =
|
||||
result = %*{
|
||||
"id": $v.id,
|
||||
"measureId": $v.measureId,
|
||||
"value": v.value,
|
||||
"timestamp": v.timestamp.formatIso8601,
|
||||
"extData": v.extData
|
||||
}
|
||||
|
@ -0,0 +1,2 @@
|
||||
-- DOWN script for logging (20190227225053)
|
||||
drop table client_log_entries;
|
12
api/src/main/sql/migrations/20190227225053-logging-up.sql
Normal file
12
api/src/main/sql/migrations/20190227225053-logging-up.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- UP script for logging (20190227225053)
|
||||
create table client_log_entries (
|
||||
id serial primary key,
|
||||
user_id uuid not null references users (id),
|
||||
"level" varchar not null,
|
||||
"scope" varchar not null,
|
||||
message varchar not null,
|
||||
stacktrace varchar not null,
|
||||
"timestamp" timestamp with time zone not null default current_timestamp
|
||||
);
|
||||
|
||||
create index client_log_entries_by_level on client_log_entries ("level");
|
Reference in New Issue
Block a user