• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

suculent / thinx-device-api / #252646965

14 Mar 2026 06:07PM UTC coverage: 71.662% (+0.4%) from 71.268%
#252646965

push

suculent
fix: restore apk install for SSL step - base image not yet rebuilt

bash/ca-certificates/openssl are added to base/Dockerfile but build-base
hasn't run yet, so the published thinxcloud/base:alpine doesn't have them.
Restore the apk add until base image is updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

1926 of 3653 branches covered (52.72%)

Branch coverage included in aggregate %.

8255 of 10554 relevant lines covered (78.22%)

13.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.59
/lib/thinx/database.js
1
// Database Manager
2

3
const Globals = require("./globals.js");
1✔
4
const app_config = Globals.app_config(); // for (deprecated/development) database_uri
1✔
5
const fs = require("fs-extra");
1✔
6

7
const Filez = require("./files.js");
1✔
8
let ROOT = Filez.appRoot();
1✔
9
module.exports = class Database {
1✔
10

11
        constructor() {
12

13
                let db_uri;
14
                let user = process.env.COUCHDB_USER;
348✔
15
                let pass = process.env.COUCHDB_PASS;
348✔
16

17
                if ((typeof (user) !== "undefined") && (typeof (pass) !== "undefined")) {
348!
18
                        db_uri = `http://${user}:${pass}@couchdb:5984`;
348✔
19
                } else {
20
                        db_uri = app_config.database_uri; // fallback to old config.json; deprecated
×
21
                        console.log("⛔️ [deprecated] Using database credentials from configuration:", db_uri);
×
22
                }
23

24
                this.db_uri = db_uri;
348✔
25
                this.nano = require("nano")(db_uri);
348✔
26

27
        }
28

29
        nano() {
30
                return this.nano;
×
31
        }
32

33
        uri() {
34

35
                /* duplicate code, happens in constructor and that's enough
36
                let db_uri;
37
                let user = process.env.COUCHDB_USER;
38
                let pass = process.env.COUCHDB_PASS;
39

40
                if ((typeof (user) !== "undefined") && (typeof (pass) !== "undefined")) {
41
                        db_uri = `http://${user}:${pass}@couchdb:5984`;
42
                } else {
43
                        db_uri = app_config.database_uri; // fallback to old config.json; deprecated
44
                        console.log("⛔️ [deprecated] Using database credentials from configuration...");
45
                }
46

47
                this.db_uri = db_uri;
48
                */
49

50
                return this.db_uri;
333✔
51
        }
52
        
53

54
        null_cb(err, body, header) {
55
                // only unexpected errors should be logged
56
                if (process.env.ENVIRONMENT === "test") {
×
57
                        // The database may already exist.
58
                        if (err.ststusCode !== 412) {
×
59
                                console.log(err, body, header);
×
60
                        }
61
                }
62
        }
63

64
        // Designated initalizer
65
        init(callback) {
66

67
                console.log("ℹ️ [info] Initializing databases...");
19✔
68

69
                let db_names = [
19✔
70
                        "devices", "builds", "users", "logs"
71
                ];
72

73
                this.nano.db.list((_err, existing_dbs) => {
19✔
74

75
                        if ((typeof(existing_dbs) === "undefined") || (existing_dbs === null)) existing_dbs = [];
19!
76

77
                        db_names.forEach((name) => {
19✔
78

79
                                if (existing_dbs.includes(name)) {
76!
80
                                        console.log(`ℹ️ [info] DB ${name} already exists.`);
×
81
                                        return;
×
82
                                }
83

84
                                const dbprefix = Globals.prefix();
76✔
85

86
                                this.nano.db.create(dbprefix + "managed_" + name).then((/* cerr, data */) => {
76✔
87
                                        let couch_db = this.nano.db.use(dbprefix + "managed_" + name);
4✔
88
                                        this.injectDesign(couch_db, name, ROOT + "/design/design_" + name + ".json");
4✔
89
                                        this.injectReplFilter(couch_db, ROOT + "/design/filters_" + name + ".json");
4✔
90
                                        console.log(`ℹ️ [info] Database managed_${name} initialized.`);
4✔
91
                                }).catch((err2) => {
92
                                        // returns error normally if DB already exists
93
                                        this.handleDatabaseErrors(err2, "managed_" + name, dbprefix);
72✔
94
                                });
95
                        });
96

97
                        this.nano.db.list((err2, new_dbs) => {
19✔
98
                                if (typeof (callback) !== "undefined") {
19!
99
                                        callback(err2, new_dbs);
19✔
100
                                } else {
101
                                        return new_dbs;
×
102
                                }
103
                        });
104

105
                        setTimeout(() => {
19✔
106
                                setInterval(this.compactDatabases, 3600 * 1000); // Compact databases once an hour        
×
107
                        }, 30000);
108
                        
109
                });
110
        }
111

112
        compactDatabases(opt_callback) {
113
                const prefix = Globals.prefix();
1✔
114
                let db_uri = new Database().uri();
1✔
115
                this.nano = require("nano")(db_uri);
1✔
116
                this.nano.db.compact(prefix + "managed_logs")
1✔
117
                .then(() => {
118
                        this.nano.db.compact(prefix + "managed_builds");
1✔
119
                }).then(() => {
120
                        this.nano.db.compact(prefix + "managed_devices");
1✔
121
                }).then(() => {
122
                        this.nano.db.compact(prefix + "managed_users");
1✔
123
                }).then(() => {
124
                        if (typeof (opt_callback) !== "undefined") opt_callback(true);
1!
125
                }).catch(e => {
126
                        console.log("InitDB compactDatabases error", e);
×
127
                        if (typeof (opt_callback) !== "undefined") opt_callback(e);
×
128
                });
129
        }
130

131
        // Database preparation on first run
132
        getDocument(file) {
133
                if (!fs.existsSync(file)) {
8!
134
                        console.log("☣️ [error] Initializing replication filter failed, file does not exist", file);
×
135
                        return false;
×
136
                }
137
                const data = fs.readFileSync(file);
8✔
138
                if (typeof (data) === "undefined") {
8!
139
                        console.log("☣️ [error] [getDocument] no data read.");
×
140
                        return false;
×
141
                }
142
                // Parser may fail
143
                try {
8✔
144
                        return JSON.parse(data);
8✔
145
                } catch (e) {
146
                        console.log("☣️ [error] Document File may not exist: " + e);
×
147
                        return false;
×
148
                }
149
        }
150

151
        logCouchError(err, body, header, tag) {
152
                if (err !== null) {
8!
153
                        if (err.toString().indexOf("conflict") === -1) {
×
154
                                console.log("☣️ [error] Couch Init error: ", err, body, header, tag);
×
155
                        }
156
                        if (err.toString().indexOf("ENOTFOUND") !== -1) {
×
157
                                console.log("Critical DB integration error, exiting.");
×
158
                                process.exit(1);
×
159
                        }
160
                } else {
161
                        return;
8✔
162
                }
163
                if (typeof (body) !== "undefined") {
×
164
                        console.log("☣️ [error] Log Couch Insert body: " + body + " " + tag);
×
165
                }
166
                if (typeof (header) !== "undefined") {
×
167
                        console.log("☣️ [error] Log Couchd Insert header: " + header + " " + tag);
×
168
                }
169
        }
170

171
        injectDesign(db, design, file) {
172
                if (typeof (design) === "undefined") return;
4!
173
                let design_doc = this.getDocument(file);
4✔
174
                if (design_doc != null) {
4!
175
                        db.insert(design_doc, "_design/" + design, (err, body, header) => {
4✔
176
                                this.logCouchError(err, body, header, "init:design:" + design); // returns if no err
4✔
177
                        });
178
                } else {
179
                        console.log("☣️ [error] Design doc injection issue at " + file);
×
180
                }
181
        }
182

183
        injectReplFilter(db, file) {
184
                let filter_doc = this.getDocument(file);
4✔
185
                if (filter_doc !== false) {
4!
186
                        db.insert(filter_doc, "_design/repl_filters", (err, body, header) => {
4✔
187
                                this.logCouchError(err, body, header, "init:repl:" + JSON.stringify(filter_doc)); // returns if no err
4✔
188
                        });
189
                } else {
190
                        console.log("☣️ [error] Filter doc injection issue (no doc) at " + file);
×
191
                }
192
        }
193

194
        handleDatabaseErrors(err, name, info) {
195
                if (err.toString().indexOf("the file already exists") !== -1) {
72!
196
                        // silently fail, this is ok
197
                } else if (err.toString().indexOf("error happened") !== -1) {
×
198
                        console.log("🚫 [critical] Database connectivity issue. " + err.toString());
×
199
                        // give some time for DB to wake up until next try, also prevents too fast restarts...
200
                        setTimeout(() => {
×
201
                                process.exit(1);
×
202
                        }, 1000);
203
                } else {
204
                        console.log("🚫 [critical] Database " + name + " creation failed. " + err, " info:", info);
×
205
                        setTimeout(() => {
×
206
                                process.exit(2);
×
207
                        }, 1000);
208
                }
209
        }
210
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc