aboutsummaryrefslogtreecommitdiff
path: root/src/update_database.nim
blob: e3eae373875ac57fac64a6105fe14b5f5caa5ecf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
## RRD database update CGI program. Takes JSON measurements from ruuvitag-upload
## (https://github.com/oswjk/ruuvitag-upload) as input and stores them in the correct RRD database.
##
## The JSON format is as follows:
##
##    {
##        "alias": {
##            "timestamp": <seconds since unix epoch>,
##            "temperature": <temperature in degrees celcius>,
##            "humidity": <relative humidity (0% - 100%)>,
##            "pressure": <pressure in kilopascals>,
##            "battery_potential": <ruuvitag battery potential in volts>
##        },
##        "another_alias": {
##            ...
##        }
##    }
##
## The program should be run with an accompanying CGI script as the main driver. The web server will
## execute the script which looks like this:
##
##    #!/path/to/update_database
##    databaseDir = /path/to/where/databases/are
##
## This will execute update_database with one argument: the path to this script file.
## update_database will read this script and parse it as a configuration file. The databaseDir
## option should point to where the .rrd databases are stored.
##
## The update_database program will use the ``alias`` from measurements as the database name (with
## ``.rrd`` extension).

import json
import os
import osproc
import parsecfg
import strformat
import strutils

type
    CgiError = object of CatchableError
        code: int

proc newCgiError(code: int, message: string): ref CgiError =
    result = newException(CgiError, message)
    result.code = code

proc readData(): string =
    var size = parseInt(getEnv("CONTENT_LENGTH").string)
    if size == 0:
        return ""
    result = newString(size)
    if readBuffer(stdin, addr result[0], size) != 0:
        raise newCgiError(400, "not enough data")

proc main() =
    let databaseDir = getAppDir().parentDir() / "database"

    if getEnv("REQUEST_METHOD") != "POST":
        raise newCgiError(400, "request method must be POST")

    let j = parseJson(readData())

    for alias, measurement in j:
        let filename = normalizedPath(joinPath(databaseDir, alias & ".rrd"))

        if not filename.startsWith(databaseDir):
            raise newCgiError(400, "invalid database")

        if not fileExists(filename):
            raise newCgiError(400, "unknown database")

        let
            timestamp = measurement["timestamp"].getBiggestInt()
            temperature = measurement["temperature"].getFloat()
            humidity = measurement["humidity"].getFloat()
            pressure = measurement["pressure"].getFloat()
            battery = measurement["battery_potential"].getFloat()
            data = &"{timestamp}:{temperature}:{humidity}:{pressure}:{battery}"
            (_, ec) = execCmdEx(&"rrdtool update {filename} {data}")

        if ec != 0:
            raise newCgiError(500, "rrdtool failed")

try:
    main()
    echo("Content-Type: text/plain")
    echo("")
except CgiError as e:
    echo(&"Status: {e.code}")
    echo("Content-Type: text/plain")
    echo("")
    echo(e.msg)
    quit(0)
except JsonParsingError as e:
    echo(&"Status: 400 Bad Request")
    echo("Content-Type: text/plain")
    echo("")
    echo(&"invalid json: {e.msg}")
    quit(0)
except Exception as e:
    echo(&"Status: 500 Internal Server Error")
    echo("Content-Type: text/plain")
    echo("")
    echo(e.msg)
    quit(0)