iobroker.time-tracker 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +386 -0
- package/admin/i18n/de.json +34 -0
- package/admin/i18n/en.json +1 -0
- package/admin/i18n/es.json +34 -0
- package/admin/i18n/fr.json +34 -0
- package/admin/i18n/it.json +34 -0
- package/admin/i18n/nl.json +34 -0
- package/admin/i18n/pl.json +34 -0
- package/admin/i18n/pt.json +34 -0
- package/admin/i18n/ru.json +34 -0
- package/admin/i18n/uk.json +34 -0
- package/admin/i18n/zh-cn.json +34 -0
- package/admin/jsonConfig.json +266 -0
- package/admin/time-tracker.png +0 -0
- package/build/lib/adapter/automation.js +100 -0
- package/build/lib/adapter/automation.js.map +7 -0
- package/build/lib/adapter/commands.js +160 -0
- package/build/lib/adapter/commands.js.map +7 -0
- package/build/lib/adapter/messages.js +207 -0
- package/build/lib/adapter/messages.js.map +7 -0
- package/build/lib/adapter/presence.js +129 -0
- package/build/lib/adapter/presence.js.map +7 -0
- package/build/lib/adapter/states.js +330 -0
- package/build/lib/adapter/states.js.map +7 -0
- package/build/lib/adapter/triggers.js +92 -0
- package/build/lib/adapter/triggers.js.map +7 -0
- package/build/lib/db/database.js +96 -0
- package/build/lib/db/database.js.map +7 -0
- package/build/lib/db/migrations.js +603 -0
- package/build/lib/db/migrations.js.map +7 -0
- package/build/lib/db/repositories/absences.js +432 -0
- package/build/lib/db/repositories/absences.js.map +7 -0
- package/build/lib/db/repositories/audit.js +90 -0
- package/build/lib/db/repositories/audit.js.map +7 -0
- package/build/lib/db/repositories/automations.js +326 -0
- package/build/lib/db/repositories/automations.js.map +7 -0
- package/build/lib/db/repositories/entries.js +335 -0
- package/build/lib/db/repositories/entries.js.map +7 -0
- package/build/lib/db/repositories/holidays.js +156 -0
- package/build/lib/db/repositories/holidays.js.map +7 -0
- package/build/lib/db/repositories/payouts.js +221 -0
- package/build/lib/db/repositories/payouts.js.map +7 -0
- package/build/lib/db/repositories/rfid.js +264 -0
- package/build/lib/db/repositories/rfid.js.map +7 -0
- package/build/lib/db/repositories/rules.js +356 -0
- package/build/lib/db/repositories/rules.js.map +7 -0
- package/build/lib/db/repositories/settings.js +91 -0
- package/build/lib/db/repositories/settings.js.map +7 -0
- package/build/lib/db/repositories/terminals.js +243 -0
- package/build/lib/db/repositories/terminals.js.map +7 -0
- package/build/lib/db/repositories/triggers.js +240 -0
- package/build/lib/db/repositories/triggers.js.map +7 -0
- package/build/lib/db/repositories/users.js +491 -0
- package/build/lib/db/repositories/users.js.map +7 -0
- package/build/lib/db/seed.js +180 -0
- package/build/lib/db/seed.js.map +7 -0
- package/build/lib/domain/avatar.js +63 -0
- package/build/lib/domain/avatar.js.map +7 -0
- package/build/lib/domain/breaks.js +70 -0
- package/build/lib/domain/breaks.js.map +7 -0
- package/build/lib/domain/calculation.js +150 -0
- package/build/lib/domain/calculation.js.map +7 -0
- package/build/lib/domain/holidays.js +113 -0
- package/build/lib/domain/holidays.js.map +7 -0
- package/build/lib/domain/punch.js +99 -0
- package/build/lib/domain/punch.js.map +7 -0
- package/build/lib/domain/target.js +89 -0
- package/build/lib/domain/target.js.map +7 -0
- package/build/lib/errors.js +56 -0
- package/build/lib/errors.js.map +7 -0
- package/build/lib/reports/labels.js +358 -0
- package/build/lib/reports/labels.js.map +7 -0
- package/build/lib/reports/pdf.js +308 -0
- package/build/lib/reports/pdf.js.map +7 -0
- package/build/lib/reports/types.js +32 -0
- package/build/lib/reports/types.js.map +7 -0
- package/build/lib/reports/xls.js +211 -0
- package/build/lib/reports/xls.js.map +7 -0
- package/build/lib/services/aggregation.js +495 -0
- package/build/lib/services/aggregation.js.map +7 -0
- package/build/lib/services/auth.js +381 -0
- package/build/lib/services/auth.js.map +7 -0
- package/build/lib/services/backup.js +372 -0
- package/build/lib/services/backup.js.map +7 -0
- package/build/lib/services/closing.js +106 -0
- package/build/lib/services/closing.js.map +7 -0
- package/build/lib/services/sessionSecret.js +100 -0
- package/build/lib/services/sessionSecret.js.map +7 -0
- package/build/lib/services/sync.js +278 -0
- package/build/lib/services/sync.js.map +7 -0
- package/build/lib/util/time.js +125 -0
- package/build/lib/util/time.js.map +7 -0
- package/build/lib/web/api.js +2589 -0
- package/build/lib/web/api.js.map +7 -0
- package/build/lib/web/cookies.js +80 -0
- package/build/lib/web/cookies.js.map +7 -0
- package/build/lib/web/events.js +50 -0
- package/build/lib/web/events.js.map +7 -0
- package/build/lib/web/pin-guard.js +86 -0
- package/build/lib/web/pin-guard.js.map +7 -0
- package/build/lib/web/problem.js +129 -0
- package/build/lib/web/problem.js.map +7 -0
- package/build/lib/web/rate-limit.js +65 -0
- package/build/lib/web/rate-limit.js.map +7 -0
- package/build/lib/web/router.js +274 -0
- package/build/lib/web/router.js.map +7 -0
- package/build/lib/web/server.js +189 -0
- package/build/lib/web/server.js.map +7 -0
- package/build/lib/web/static.js +151 -0
- package/build/lib/web/static.js.map +7 -0
- package/build/lib/web/stream.js +135 -0
- package/build/lib/web/stream.js.map +7 -0
- package/build/main.js +986 -0
- package/build/main.js.map +7 -0
- package/io-package.json +221 -0
- package/package.json +131 -0
- package/src-pwa/src/i18n/de.json +359 -0
- package/src-pwa/src/i18n/en.json +359 -0
- package/src-pwa/src/i18n/es.json +359 -0
- package/src-pwa/src/i18n/fr.json +359 -0
- package/src-pwa/src/i18n/index.ts +96 -0
- package/src-pwa/src/i18n/it.json +359 -0
- package/src-pwa/src/i18n/nl.json +359 -0
- package/src-pwa/src/i18n/pl.json +359 -0
- package/src-pwa/src/i18n/pt.json +359 -0
- package/src-pwa/src/i18n/ru.json +359 -0
- package/src-pwa/src/i18n/uk.json +359 -0
- package/src-pwa/src/i18n/zh-cn.json +359 -0
- package/www/assets/index-QI3wrEvW.js +119 -0
- package/www/assets/index-QI3wrEvW.js.map +1 -0
- package/www/assets/workbox-window.prod.es5-Bd17z0YL.js +3 -0
- package/www/assets/workbox-window.prod.es5-Bd17z0YL.js.map +1 -0
- package/www/favicon.svg +16 -0
- package/www/icon-192.png +0 -0
- package/www/icon-512.png +0 -0
- package/www/index.html +38 -0
- package/www/manifest.webmanifest +1 -0
- package/www/person.png +0 -0
- package/www/sw.js +3 -0
- package/www/sw.js.map +1 -0
- package/www/workbox-9c191d2f.js +3 -0
- package/www/workbox-9c191d2f.js.map +1 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var users_exports = {};
|
|
20
|
+
__export(users_exports, {
|
|
21
|
+
LoginExistsError: () => LoginExistsError,
|
|
22
|
+
UnknownRoleError: () => UnknownRoleError,
|
|
23
|
+
createUsersRepository: () => createUsersRepository,
|
|
24
|
+
mapUserRow: () => mapUserRow,
|
|
25
|
+
mapWorkProfileRow: () => mapWorkProfileRow
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(users_exports);
|
|
28
|
+
var import_errors = require("../../errors");
|
|
29
|
+
var import_audit = require("./audit");
|
|
30
|
+
class LoginExistsError extends Error {
|
|
31
|
+
/**
|
|
32
|
+
* Creates the error.
|
|
33
|
+
*
|
|
34
|
+
* @param login - login name that is already taken
|
|
35
|
+
*/
|
|
36
|
+
constructor(login) {
|
|
37
|
+
super(`login "${login}" already exists`);
|
|
38
|
+
this.login = login;
|
|
39
|
+
this.name = "LoginExistsError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
class UnknownRoleError extends Error {
|
|
43
|
+
/**
|
|
44
|
+
* Creates the error.
|
|
45
|
+
*
|
|
46
|
+
* @param roleKey - unknown role key
|
|
47
|
+
*/
|
|
48
|
+
constructor(roleKey) {
|
|
49
|
+
super(`unknown role "${roleKey}"`);
|
|
50
|
+
this.roleKey = roleKey;
|
|
51
|
+
this.name = "UnknownRoleError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function mapUserRow(row) {
|
|
55
|
+
return {
|
|
56
|
+
id: row.id,
|
|
57
|
+
login: row.login,
|
|
58
|
+
passwordHash: row.password_hash,
|
|
59
|
+
displayName: row.display_name,
|
|
60
|
+
email: row.email,
|
|
61
|
+
rfidCard: row.rfid_card,
|
|
62
|
+
isActive: row.is_active !== 0,
|
|
63
|
+
mustChangePw: row.must_change_pw !== 0,
|
|
64
|
+
locale: row.locale,
|
|
65
|
+
timezone: row.timezone,
|
|
66
|
+
pinHash: row.pin_hash,
|
|
67
|
+
avatar: row.avatar,
|
|
68
|
+
createdAt: row.created_at,
|
|
69
|
+
updatedAt: row.updated_at,
|
|
70
|
+
lastLoginAt: row.last_login_at
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function mapWorkProfileRow(row) {
|
|
74
|
+
return {
|
|
75
|
+
userId: row.user_id,
|
|
76
|
+
percent: row.percent,
|
|
77
|
+
weeklyHours: row.weekly_hours,
|
|
78
|
+
workdays: row.workdays,
|
|
79
|
+
startDate: row.start_date,
|
|
80
|
+
endDate: row.end_date,
|
|
81
|
+
overtimeCarryover: row.overtime_carryover,
|
|
82
|
+
vorholzeitPerYear: row.vorholzeit_per_year,
|
|
83
|
+
vacationCarryover: row.vacation_carryover,
|
|
84
|
+
vacationPerYear: row.vacation_per_year,
|
|
85
|
+
overtimeModel: row.overtime_model,
|
|
86
|
+
holidayFlags: row.holiday_flags,
|
|
87
|
+
pausePaidMinutes: row.pause_paid_minutes
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const USER_COLUMNS = `id, login, password_hash, display_name, email, rfid_card, is_active,
|
|
91
|
+
must_change_pw, locale, timezone, pin_hash, avatar, created_at, updated_at, last_login_at`;
|
|
92
|
+
const PROFILE_COLUMNS = `user_id, percent, weekly_hours, workdays, start_date, end_date, overtime_carryover,
|
|
93
|
+
vorholzeit_per_year, vacation_carryover, vacation_per_year, overtime_model, holiday_flags, pause_paid_minutes`;
|
|
94
|
+
const AUDITED_USER_FIELDS = [
|
|
95
|
+
"login",
|
|
96
|
+
"displayName",
|
|
97
|
+
"email",
|
|
98
|
+
"rfidCard",
|
|
99
|
+
"isActive",
|
|
100
|
+
"mustChangePw",
|
|
101
|
+
"locale",
|
|
102
|
+
"timezone"
|
|
103
|
+
];
|
|
104
|
+
const AUDITED_PROFILE_FIELDS = [
|
|
105
|
+
"percent",
|
|
106
|
+
"weeklyHours",
|
|
107
|
+
"workdays",
|
|
108
|
+
"startDate",
|
|
109
|
+
"endDate",
|
|
110
|
+
"overtimeCarryover",
|
|
111
|
+
"vorholzeitPerYear",
|
|
112
|
+
"vacationCarryover",
|
|
113
|
+
"vacationPerYear",
|
|
114
|
+
"overtimeModel",
|
|
115
|
+
"holidayFlags",
|
|
116
|
+
"pausePaidMinutes"
|
|
117
|
+
];
|
|
118
|
+
function createUsersRepository(db) {
|
|
119
|
+
const selectById = db.prepare(`SELECT ${USER_COLUMNS} FROM users WHERE id = ?`);
|
|
120
|
+
const selectByLogin = db.prepare(`SELECT ${USER_COLUMNS} FROM users WHERE login = ? COLLATE NOCASE`);
|
|
121
|
+
const selectByRfid = db.prepare(`SELECT ${USER_COLUMNS} FROM users WHERE rfid_card = ? COLLATE NOCASE LIMIT 1`);
|
|
122
|
+
const setPinHash = db.prepare("UPDATE users SET pin_hash = ?, updated_at = ? WHERE id = ?");
|
|
123
|
+
const setAvatarData = db.prepare("UPDATE users SET avatar = ?, updated_at = ? WHERE id = ?");
|
|
124
|
+
const selectAll = db.prepare(`SELECT ${USER_COLUMNS} FROM users ORDER BY display_name COLLATE NOCASE, id`);
|
|
125
|
+
const selectActive = db.prepare(
|
|
126
|
+
`SELECT ${USER_COLUMNS} FROM users WHERE is_active = 1 ORDER BY display_name COLLATE NOCASE, id`
|
|
127
|
+
);
|
|
128
|
+
const insertUser = db.prepare(
|
|
129
|
+
`INSERT INTO users
|
|
130
|
+
(login, password_hash, display_name, email, rfid_card, is_active, must_change_pw,
|
|
131
|
+
locale, timezone, created_at, updated_at)
|
|
132
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
133
|
+
);
|
|
134
|
+
const updateUser = db.prepare(
|
|
135
|
+
`UPDATE users SET display_name = ?, email = ?, locale = ?, timezone = ?, rfid_card = ?,
|
|
136
|
+
is_active = ?, must_change_pw = ?, password_hash = ?, updated_at = ?
|
|
137
|
+
WHERE id = ?`
|
|
138
|
+
);
|
|
139
|
+
const updateLastLogin = db.prepare("UPDATE users SET last_login_at = ? WHERE id = ?");
|
|
140
|
+
const insertDefaultProfile = db.prepare("INSERT OR IGNORE INTO work_profiles (user_id) VALUES (?)");
|
|
141
|
+
const selectProfile = db.prepare(`SELECT ${PROFILE_COLUMNS} FROM work_profiles WHERE user_id = ?`);
|
|
142
|
+
const upsertProfile = db.prepare(
|
|
143
|
+
`INSERT INTO work_profiles (${PROFILE_COLUMNS})
|
|
144
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
145
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
146
|
+
percent = excluded.percent,
|
|
147
|
+
weekly_hours = excluded.weekly_hours,
|
|
148
|
+
workdays = excluded.workdays,
|
|
149
|
+
start_date = excluded.start_date,
|
|
150
|
+
end_date = excluded.end_date,
|
|
151
|
+
overtime_carryover = excluded.overtime_carryover,
|
|
152
|
+
vorholzeit_per_year = excluded.vorholzeit_per_year,
|
|
153
|
+
vacation_carryover = excluded.vacation_carryover,
|
|
154
|
+
vacation_per_year = excluded.vacation_per_year,
|
|
155
|
+
overtime_model = excluded.overtime_model,
|
|
156
|
+
holiday_flags = excluded.holiday_flags,
|
|
157
|
+
pause_paid_minutes = excluded.pause_paid_minutes`
|
|
158
|
+
);
|
|
159
|
+
const selectRoles = db.prepare(
|
|
160
|
+
`SELECT r.key FROM user_roles ur JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = ? ORDER BY r.key`
|
|
161
|
+
);
|
|
162
|
+
const selectRoleId = db.prepare("SELECT id FROM roles WHERE key = ?");
|
|
163
|
+
const deleteRoles = db.prepare("DELETE FROM user_roles WHERE user_id = ?");
|
|
164
|
+
const insertRole = db.prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
|
|
165
|
+
const selectPermissions = db.prepare(
|
|
166
|
+
`SELECT DISTINCT p.key FROM user_roles ur
|
|
167
|
+
JOIN role_permissions rp ON rp.role_id = ur.role_id
|
|
168
|
+
JOIN permissions p ON p.id = rp.permission_id
|
|
169
|
+
WHERE ur.user_id = ? ORDER BY p.key`
|
|
170
|
+
);
|
|
171
|
+
const selectRoleCatalog = db.prepare(
|
|
172
|
+
`SELECT r.key AS key, r.name AS name, p.key AS permission
|
|
173
|
+
FROM roles r
|
|
174
|
+
LEFT JOIN role_permissions rp ON rp.role_id = r.id
|
|
175
|
+
LEFT JOIN permissions p ON p.id = rp.permission_id
|
|
176
|
+
ORDER BY r.key, p.key`
|
|
177
|
+
);
|
|
178
|
+
const read = (id) => {
|
|
179
|
+
const row = selectById.get(id);
|
|
180
|
+
return row ? mapUserRow(row) : null;
|
|
181
|
+
};
|
|
182
|
+
const readProfile = (userId) => {
|
|
183
|
+
const row = selectProfile.get(userId);
|
|
184
|
+
return row ? mapWorkProfileRow(row) : null;
|
|
185
|
+
};
|
|
186
|
+
const defaultProfile = (userId) => ({
|
|
187
|
+
userId,
|
|
188
|
+
percent: 100,
|
|
189
|
+
weeklyHours: 42.5,
|
|
190
|
+
workdays: "0;1;1;1;1;1;0",
|
|
191
|
+
startDate: null,
|
|
192
|
+
endDate: null,
|
|
193
|
+
overtimeCarryover: 0,
|
|
194
|
+
vorholzeitPerYear: 0,
|
|
195
|
+
vacationCarryover: 0,
|
|
196
|
+
vacationPerYear: 0,
|
|
197
|
+
overtimeModel: "monthly",
|
|
198
|
+
holidayFlags: null,
|
|
199
|
+
pausePaidMinutes: 0
|
|
200
|
+
});
|
|
201
|
+
const resolveRoleIds = (roleKeys) => roleKeys.map((key) => {
|
|
202
|
+
const row = selectRoleId.get(key);
|
|
203
|
+
if (!row) {
|
|
204
|
+
throw new UnknownRoleError(key);
|
|
205
|
+
}
|
|
206
|
+
return row.id;
|
|
207
|
+
});
|
|
208
|
+
const writeRoles = (userId, roleIds) => {
|
|
209
|
+
deleteRoles.run(userId);
|
|
210
|
+
for (const roleId of roleIds) {
|
|
211
|
+
insertRole.run(userId, roleId);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
create(input) {
|
|
216
|
+
var _a, _b;
|
|
217
|
+
const login = input.login.trim();
|
|
218
|
+
if (!login) {
|
|
219
|
+
throw new import_errors.ValidationError("login must not be empty");
|
|
220
|
+
}
|
|
221
|
+
const existing = selectByLogin.get(login);
|
|
222
|
+
if (existing) {
|
|
223
|
+
throw new LoginExistsError(login);
|
|
224
|
+
}
|
|
225
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
226
|
+
const roleKeys = (_b = input.roleKeys) != null ? _b : [];
|
|
227
|
+
const roleIds = resolveRoleIds(roleKeys);
|
|
228
|
+
let userId = 0;
|
|
229
|
+
const run = db.transaction(() => {
|
|
230
|
+
var _a2, _b2, _c, _d, _e, _f, _g;
|
|
231
|
+
const result = insertUser.run(
|
|
232
|
+
login,
|
|
233
|
+
(_a2 = input.passwordHash) != null ? _a2 : "",
|
|
234
|
+
input.displayName,
|
|
235
|
+
(_b2 = input.email) != null ? _b2 : null,
|
|
236
|
+
(_c = input.rfidCard) != null ? _c : null,
|
|
237
|
+
1,
|
|
238
|
+
input.mustChangePw === true ? 1 : 0,
|
|
239
|
+
(_d = input.locale) != null ? _d : "de-DE",
|
|
240
|
+
(_e = input.timezone) != null ? _e : "Europe/Berlin",
|
|
241
|
+
now,
|
|
242
|
+
now
|
|
243
|
+
);
|
|
244
|
+
userId = Number(result.lastInsertRowid);
|
|
245
|
+
insertDefaultProfile.run(userId);
|
|
246
|
+
writeRoles(userId, roleIds);
|
|
247
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
248
|
+
atUtc: now,
|
|
249
|
+
actorId: (_f = input.actorId) != null ? _f : null,
|
|
250
|
+
action: "user.create",
|
|
251
|
+
entity: "user",
|
|
252
|
+
entityId: userId,
|
|
253
|
+
detail: { login, displayName: input.displayName, roles: roleKeys.slice().sort() },
|
|
254
|
+
ip: (_g = input.actorIp) != null ? _g : null
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
run();
|
|
258
|
+
const created = read(userId);
|
|
259
|
+
if (!created) {
|
|
260
|
+
throw new Error(`user ${userId} disappeared right after creation`);
|
|
261
|
+
}
|
|
262
|
+
return created;
|
|
263
|
+
},
|
|
264
|
+
findById: read,
|
|
265
|
+
findByLogin(login) {
|
|
266
|
+
const row = selectByLogin.get(login.trim());
|
|
267
|
+
return row ? mapUserRow(row) : null;
|
|
268
|
+
},
|
|
269
|
+
findByRfidCard(card) {
|
|
270
|
+
const row = selectByRfid.get(card.trim());
|
|
271
|
+
return row ? mapUserRow(row) : null;
|
|
272
|
+
},
|
|
273
|
+
setPin(input) {
|
|
274
|
+
var _a;
|
|
275
|
+
if (!read(input.userId)) {
|
|
276
|
+
throw new import_errors.NotFoundError(`user ${input.userId} not found`);
|
|
277
|
+
}
|
|
278
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
279
|
+
const run = db.transaction(() => {
|
|
280
|
+
var _a2;
|
|
281
|
+
setPinHash.run(input.pinHash, now, input.userId);
|
|
282
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
283
|
+
atUtc: now,
|
|
284
|
+
actorId: input.actorId,
|
|
285
|
+
action: "user.pin",
|
|
286
|
+
entity: "user",
|
|
287
|
+
entityId: input.userId,
|
|
288
|
+
// the hash itself never reaches the audit trail
|
|
289
|
+
detail: { cleared: input.pinHash === null },
|
|
290
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
run();
|
|
294
|
+
},
|
|
295
|
+
setAvatar(input) {
|
|
296
|
+
var _a;
|
|
297
|
+
if (!read(input.userId)) {
|
|
298
|
+
throw new import_errors.NotFoundError(`user ${input.userId} not found`);
|
|
299
|
+
}
|
|
300
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
301
|
+
const run = db.transaction(() => {
|
|
302
|
+
var _a2;
|
|
303
|
+
setAvatarData.run(input.avatar, now, input.userId);
|
|
304
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
305
|
+
atUtc: now,
|
|
306
|
+
actorId: input.actorId,
|
|
307
|
+
action: "user.avatar",
|
|
308
|
+
entity: "user",
|
|
309
|
+
entityId: input.userId,
|
|
310
|
+
// the picture itself never reaches the audit trail
|
|
311
|
+
detail: { cleared: input.avatar === null },
|
|
312
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
run();
|
|
316
|
+
},
|
|
317
|
+
list(options) {
|
|
318
|
+
const rows = ((options == null ? void 0 : options.includeInactive) === true ? selectAll : selectActive).all();
|
|
319
|
+
return rows.map(mapUserRow);
|
|
320
|
+
},
|
|
321
|
+
update(input) {
|
|
322
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
323
|
+
const current = read(input.id);
|
|
324
|
+
if (!current) {
|
|
325
|
+
throw new import_errors.NotFoundError(`user ${input.id} not found`);
|
|
326
|
+
}
|
|
327
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
328
|
+
const next = {
|
|
329
|
+
...current,
|
|
330
|
+
displayName: (_b = input.patch.displayName) != null ? _b : current.displayName,
|
|
331
|
+
email: input.patch.email === void 0 ? current.email : input.patch.email,
|
|
332
|
+
locale: (_c = input.patch.locale) != null ? _c : current.locale,
|
|
333
|
+
timezone: (_d = input.patch.timezone) != null ? _d : current.timezone,
|
|
334
|
+
rfidCard: input.patch.rfidCard === void 0 ? current.rfidCard : input.patch.rfidCard,
|
|
335
|
+
isActive: (_e = input.patch.isActive) != null ? _e : current.isActive,
|
|
336
|
+
mustChangePw: (_f = input.patch.mustChangePw) != null ? _f : current.mustChangePw,
|
|
337
|
+
passwordHash: (_g = input.patch.passwordHash) != null ? _g : current.passwordHash
|
|
338
|
+
};
|
|
339
|
+
const changes = (0, import_audit.diffFields)(
|
|
340
|
+
current,
|
|
341
|
+
next,
|
|
342
|
+
AUDITED_USER_FIELDS
|
|
343
|
+
);
|
|
344
|
+
const passwordChanged = next.passwordHash !== current.passwordHash;
|
|
345
|
+
if (Object.keys(changes).length === 0 && !passwordChanged) {
|
|
346
|
+
return current;
|
|
347
|
+
}
|
|
348
|
+
const action = !current.isActive && next.isActive ? "user.activate" : current.isActive && !next.isActive ? "user.deactivate" : "user.update";
|
|
349
|
+
const run = db.transaction(() => {
|
|
350
|
+
var _a2, _b2;
|
|
351
|
+
updateUser.run(
|
|
352
|
+
next.displayName,
|
|
353
|
+
next.email,
|
|
354
|
+
next.locale,
|
|
355
|
+
next.timezone,
|
|
356
|
+
next.rfidCard,
|
|
357
|
+
next.isActive ? 1 : 0,
|
|
358
|
+
next.mustChangePw ? 1 : 0,
|
|
359
|
+
next.passwordHash,
|
|
360
|
+
now,
|
|
361
|
+
input.id
|
|
362
|
+
);
|
|
363
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
364
|
+
atUtc: now,
|
|
365
|
+
actorId: input.actorId,
|
|
366
|
+
action,
|
|
367
|
+
entity: "user",
|
|
368
|
+
entityId: input.id,
|
|
369
|
+
// never write the password hash into the audit trail
|
|
370
|
+
detail: { changes, passwordChanged, reason: (_a2 = input.reason) != null ? _a2 : null },
|
|
371
|
+
ip: (_b2 = input.actorIp) != null ? _b2 : null
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
run();
|
|
375
|
+
const updated = read(input.id);
|
|
376
|
+
if (!updated) {
|
|
377
|
+
throw new Error(`user ${input.id} disappeared right after the update`);
|
|
378
|
+
}
|
|
379
|
+
return updated;
|
|
380
|
+
},
|
|
381
|
+
roles(userId) {
|
|
382
|
+
return selectRoles.all(userId).map((row) => row.key);
|
|
383
|
+
},
|
|
384
|
+
roleCatalog() {
|
|
385
|
+
var _a;
|
|
386
|
+
const rows = selectRoleCatalog.all();
|
|
387
|
+
const catalog = /* @__PURE__ */ new Map();
|
|
388
|
+
for (const row of rows) {
|
|
389
|
+
const entry = (_a = catalog.get(row.key)) != null ? _a : { key: row.key, name: row.name, permissions: [] };
|
|
390
|
+
if (row.permission) {
|
|
391
|
+
entry.permissions.push(row.permission);
|
|
392
|
+
}
|
|
393
|
+
catalog.set(row.key, entry);
|
|
394
|
+
}
|
|
395
|
+
return [...catalog.values()];
|
|
396
|
+
},
|
|
397
|
+
setRoles(input) {
|
|
398
|
+
var _a;
|
|
399
|
+
if (!read(input.userId)) {
|
|
400
|
+
throw new import_errors.NotFoundError(`user ${input.userId} not found`);
|
|
401
|
+
}
|
|
402
|
+
const roleIds = resolveRoleIds(input.roleKeys);
|
|
403
|
+
const before = this.roles(input.userId);
|
|
404
|
+
const after = input.roleKeys.slice().sort();
|
|
405
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
406
|
+
const run = db.transaction(() => {
|
|
407
|
+
var _a2;
|
|
408
|
+
writeRoles(input.userId, roleIds);
|
|
409
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
410
|
+
atUtc: now,
|
|
411
|
+
actorId: input.actorId,
|
|
412
|
+
action: "user.roles",
|
|
413
|
+
entity: "user",
|
|
414
|
+
entityId: input.userId,
|
|
415
|
+
detail: { old: before, new: after },
|
|
416
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
run();
|
|
420
|
+
return this.roles(input.userId);
|
|
421
|
+
},
|
|
422
|
+
permissions(userId) {
|
|
423
|
+
return selectPermissions.all(userId).map((row) => row.key);
|
|
424
|
+
},
|
|
425
|
+
hasPermissions(userId, required) {
|
|
426
|
+
const granted = new Set(this.permissions(userId));
|
|
427
|
+
return required.every((permission) => granted.has(permission));
|
|
428
|
+
},
|
|
429
|
+
getWorkProfile: readProfile,
|
|
430
|
+
saveWorkProfile(input) {
|
|
431
|
+
var _a;
|
|
432
|
+
const current = readProfile(input.userId);
|
|
433
|
+
const base = current != null ? current : defaultProfile(input.userId);
|
|
434
|
+
const next = { ...base, ...input.profile, userId: input.userId };
|
|
435
|
+
const changes = (0, import_audit.diffFields)(
|
|
436
|
+
base,
|
|
437
|
+
next,
|
|
438
|
+
AUDITED_PROFILE_FIELDS
|
|
439
|
+
);
|
|
440
|
+
if (current && Object.keys(changes).length === 0) {
|
|
441
|
+
return current;
|
|
442
|
+
}
|
|
443
|
+
const now = (_a = input.now) != null ? _a : Math.floor(Date.now() / 1e3);
|
|
444
|
+
const run = db.transaction(() => {
|
|
445
|
+
var _a2;
|
|
446
|
+
upsertProfile.run(
|
|
447
|
+
next.userId,
|
|
448
|
+
next.percent,
|
|
449
|
+
next.weeklyHours,
|
|
450
|
+
next.workdays,
|
|
451
|
+
next.startDate,
|
|
452
|
+
next.endDate,
|
|
453
|
+
next.overtimeCarryover,
|
|
454
|
+
next.vorholzeitPerYear,
|
|
455
|
+
next.vacationCarryover,
|
|
456
|
+
next.vacationPerYear,
|
|
457
|
+
next.overtimeModel,
|
|
458
|
+
next.holidayFlags,
|
|
459
|
+
next.pausePaidMinutes
|
|
460
|
+
);
|
|
461
|
+
(0, import_audit.writeAuditLog)(db, {
|
|
462
|
+
atUtc: now,
|
|
463
|
+
actorId: input.actorId,
|
|
464
|
+
action: "user.profile",
|
|
465
|
+
entity: "work_profile",
|
|
466
|
+
entityId: input.userId,
|
|
467
|
+
detail: { created: current === null, changes },
|
|
468
|
+
ip: (_a2 = input.actorIp) != null ? _a2 : null
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
run();
|
|
472
|
+
const saved = readProfile(input.userId);
|
|
473
|
+
if (!saved) {
|
|
474
|
+
throw new Error(`work profile of user ${input.userId} disappeared right after the update`);
|
|
475
|
+
}
|
|
476
|
+
return saved;
|
|
477
|
+
},
|
|
478
|
+
recordLogin(userId, now) {
|
|
479
|
+
updateLastLogin.run(now != null ? now : Math.floor(Date.now() / 1e3), userId);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
484
|
+
0 && (module.exports = {
|
|
485
|
+
LoginExistsError,
|
|
486
|
+
UnknownRoleError,
|
|
487
|
+
createUsersRepository,
|
|
488
|
+
mapUserRow,
|
|
489
|
+
mapWorkProfileRow
|
|
490
|
+
});
|
|
491
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lib/db/repositories/users.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * User repository (`users`, `user_roles`, `work_profiles`).\n *\n * Every write records an audit entry in the same transaction; passwords and password hashes never end up\n * in the audit trail, only the fact that they were changed.\n */\n\nimport type { Db } from \"../database\";\nimport type { OvertimeModel } from \"../../domain/calculation\";\nimport type { WorkProfile } from \"../../domain/target\";\nimport { NotFoundError, ValidationError } from \"../../errors\";\nimport { writeAuditLog, diffFields } from \"./audit\";\n\n/** A user as stored in the database. */\nexport interface UserRecord {\n\t/** Primary key */\n\tid: number;\n\t/** Unique login name */\n\tlogin: string;\n\t/** Hash of the password (never leaves the server) */\n\tpasswordHash: string;\n\t/** Name shown in the UI */\n\tdisplayName: string;\n\t/** E-mail address */\n\temail: string | null;\n\t/** RFID card id */\n\trfidCard: string | null;\n\t/** False for deactivated accounts */\n\tisActive: boolean;\n\t/** True while the user has to set a new password */\n\tmustChangePw: boolean;\n\t/** Preferred language */\n\tlocale: string;\n\t/** IANA time zone of the user */\n\ttimezone: string;\n\t/** Hash of the personal kiosk PIN, `null` when none is set */\n\tpinHash: string | null;\n\t/** Picture of the employee as a data URL, `null` when none is stored */\n\tavatar: string | null;\n\t/** Instant of creation, UTC epoch seconds */\n\tcreatedAt: number;\n\t/** Instant of the last change, UTC epoch seconds */\n\tupdatedAt: number;\n\t/** Instant of the last successful login, `null` if never logged in */\n\tlastLoginAt: number | null;\n}\n\n/** Employment parameters of a user (`work_profiles`). */\nexport interface WorkProfileRecord extends WorkProfile {\n\t/** Owner of the profile */\n\tuserId: number;\n\t/** Employment level in percent */\n\tpercent: number;\n\t/** Contracted hours per week at 100 % */\n\tweeklyHours: number;\n\t/** Active working days, `0;1;\u2026` for Sunday\u2026Saturday */\n\tworkdays: string;\n\t/** First day of the employment (UTC epoch seconds) */\n\tstartDate: number | null;\n\t/** Last day of the employment (UTC epoch seconds) */\n\tendDate: number | null;\n\t/** Overtime taken over from the previous year, in minutes */\n\tovertimeCarryover: number;\n\t/** Annual \u201CVorholzeit\u201D in minutes */\n\tvorholzeitPerYear: number;\n\t/** Vacation days taken over from the previous year */\n\tvacationCarryover: number;\n\t/** Annual vacation entitlement in days */\n\tvacationPerYear: number;\n\t/** Overtime model used for the reports */\n\tovertimeModel: OvertimeModel;\n\t/** Country specific holiday flags (JSON), `null` = instance default */\n\tholidayFlags: string | null;\n\t/** Minutes of the break per day that are paid (0 = the break is not paid at all) */\n\tpausePaidMinutes: number;\n}\n\n/** Input for creating a user. */\nexport interface CreateUserInput {\n\t/** Unique login name */\n\tlogin: string;\n\t/** Shown name */\n\tdisplayName: string;\n\t/** Password hash (already hashed by the caller) */\n\tpasswordHash?: string;\n\t/** E-mail address */\n\temail?: string | null;\n\t/** Preferred language, default `de-DE` */\n\tlocale?: string;\n\t/** IANA time zone, default `Europe/Berlin` */\n\ttimezone?: string;\n\t/** RFID card id */\n\trfidCard?: string | null;\n\t/** Forces a password change on the next login */\n\tmustChangePw?: boolean;\n\t/** Roles to assign (all permissions are granted by the seed roles) */\n\troleKeys?: string[];\n\t/** Id of the user who is an additional employee of the new account (self service) */\n\tactorId?: number | null;\n\t/** Client IP address */\n\tactorIp?: string | null;\n\t/** Instant of creation, defaults to now */\n\tnow?: number;\n}\n\n/** Input for changing a user. */\nexport interface UpdateUserInput {\n\t/** Id of the changed user */\n\tid: number;\n\t/** Fields to change */\n\tpatch: {\n\t\tdisplayName?: string;\n\t\temail?: string | null;\n\t\tlocale?: string;\n\t\ttimezone?: string;\n\t\trfidCard?: string | null;\n\t\tisActive?: boolean;\n\t\tmustChangePw?: boolean;\n\t\t/** Password hash (already hashed by the caller) */\n\t\tpasswordHash?: string;\n\t};\n\t/** Reason of the change (recorded in the audit trail) */\n\treason?: string | null;\n\t/** Who performs the change */\n\tactorId: number;\n\t/** Client IP address of the actor */\n\tactorIp?: string | null;\n\t/** Instant of the change, defaults to now */\n\tnow?: number;\n}\n\n/** Thrown when a login name is already taken. */\nexport class LoginExistsError extends Error {\n\t/**\n\t * Creates the error.\n\t *\n\t * @param login - login name that is already taken\n\t */\n\tconstructor(public readonly login: string) {\n\t\tsuper(`login \"${login}\" already exists`);\n\t\tthis.name = \"LoginExistsError\";\n\t}\n}\n\n/** Thrown when a role key is not known. */\nexport class UnknownRoleError extends Error {\n\t/**\n\t * Creates the error.\n\t *\n\t * @param roleKey - unknown role key\n\t */\n\tconstructor(public readonly roleKey: string) {\n\t\tsuper(`unknown role \"${roleKey}\"`);\n\t\tthis.name = \"UnknownRoleError\";\n\t}\n}\n\n/** User storage operations. */\nexport interface UsersRepository {\n\t/** Creates a user including a default work profile */\n\tcreate(input: CreateUserInput): UserRecord;\n\t/** Reads a user by id */\n\tfindById(id: number): UserRecord | null;\n\t/** Reads a user by login (case insensitive) */\n\tfindByLogin(login: string): UserRecord | null;\n\t/** Reads a user by the RFID card of the badge terminal */\n\tfindByRfidCard(card: string): UserRecord | null;\n\t/** Stores or clears the personal kiosk PIN (never written to the audit trail) */\n\tsetPin(input: {\n\t\tuserId: number;\n\t\tpinHash: string | null;\n\t\tactorId: number;\n\t\tactorIp?: string | null;\n\t\tnow?: number;\n\t}): void;\n\t/** Stores or clears the picture of an employee (the image itself never reaches the audit trail) */\n\tsetAvatar(input: {\n\t\t/** Id of the employee */\n\t\tuserId: number;\n\t\t/** Data URL of the picture, `null` clears it */\n\t\tavatar: string | null;\n\t\t/** Who changes the picture */\n\t\tactorId: number;\n\t\t/** Client IP address of the actor */\n\t\tactorIp?: string | null;\n\t\t/** Instant of the change, defaults to now */\n\t\tnow?: number;\n\t}): void;\n\t/** All users, optionally including the deactivated ones */\n\tlist(options?: { includeInactive?: boolean }): UserRecord[];\n\t/** Changes a user and audits the changed fields */\n\tupdate(input: UpdateUserInput): UserRecord;\n\t/** Role keys of a user (sorted) */\n\troles(userId: number): string[];\n\t/** All roles with their permission keys, sorted by key */\n\troleCatalog(): { key: string; name: string; permissions: string[] }[];\n\t/** Replaces the roles of a user */\n\tsetRoles(input: {\n\t\tuserId: number;\n\t\troleKeys: string[];\n\t\tactorId: number;\n\t\tactorIp?: string | null;\n\t\tnow?: number;\n\t}): string[];\n\t/** Effective permission keys of a user (sorted) */\n\tpermissions(userId: number): string[];\n\t/** Checks whether a user holds all given permissions */\n\thasPermissions(userId: number, required: string[]): boolean;\n\t/** Work profile of a user, `null` when none is stored */\n\tgetWorkProfile(userId: number): WorkProfileRecord | null;\n\t/** Creates or updates the work profile */\n\tsaveWorkProfile(input: {\n\t\tuserId: number;\n\t\tprofile: Partial<Omit<WorkProfileRecord, \"userId\">>;\n\t\tactorId: number;\n\t\tactorIp?: string | null;\n\t\tnow?: number;\n\t}): WorkProfileRecord;\n\t/** Stores the instant of the last successful login */\n\trecordLogin(userId: number, now?: number): void;\n}\n\ninterface UserRow {\n\tid: number;\n\tlogin: string;\n\tpassword_hash: string;\n\tdisplay_name: string;\n\temail: string | null;\n\trfid_card: string | null;\n\tis_active: number;\n\tmust_change_pw: number;\n\tlocale: string;\n\ttimezone: string;\n\tpin_hash: string | null;\n\tavatar: string | null;\n\tcreated_at: number;\n\tupdated_at: number;\n\tlast_login_at: number | null;\n}\n\ninterface WorkProfileRow {\n\tuser_id: number;\n\tpercent: number;\n\tweekly_hours: number;\n\tworkdays: string;\n\tstart_date: number | null;\n\tend_date: number | null;\n\tovertime_carryover: number;\n\tvorholzeit_per_year: number;\n\tvacation_carryover: number;\n\tvacation_per_year: number;\n\tovertime_model: OvertimeModel;\n\tholiday_flags: string | null;\n\tpause_paid_minutes: number;\n}\n\n/**\n * Maps a database row to a user record.\n *\n * @param row - raw database row\n * @returns user record\n */\nexport function mapUserRow(row: UserRow): UserRecord {\n\treturn {\n\t\tid: row.id,\n\t\tlogin: row.login,\n\t\tpasswordHash: row.password_hash,\n\t\tdisplayName: row.display_name,\n\t\temail: row.email,\n\t\trfidCard: row.rfid_card,\n\t\tisActive: row.is_active !== 0,\n\t\tmustChangePw: row.must_change_pw !== 0,\n\t\tlocale: row.locale,\n\t\ttimezone: row.timezone,\n\t\tpinHash: row.pin_hash,\n\t\tavatar: row.avatar,\n\t\tcreatedAt: row.created_at,\n\t\tupdatedAt: row.updated_at,\n\t\tlastLoginAt: row.last_login_at,\n\t};\n}\n\n/**\n * Maps a database row to a work profile record.\n *\n * @param row - raw database row\n * @returns work profile record\n */\nexport function mapWorkProfileRow(row: WorkProfileRow): WorkProfileRecord {\n\treturn {\n\t\tuserId: row.user_id,\n\t\tpercent: row.percent,\n\t\tweeklyHours: row.weekly_hours,\n\t\tworkdays: row.workdays,\n\t\tstartDate: row.start_date,\n\t\tendDate: row.end_date,\n\t\tovertimeCarryover: row.overtime_carryover,\n\t\tvorholzeitPerYear: row.vorholzeit_per_year,\n\t\tvacationCarryover: row.vacation_carryover,\n\t\tvacationPerYear: row.vacation_per_year,\n\t\tovertimeModel: row.overtime_model,\n\t\tholidayFlags: row.holiday_flags,\n\t\tpausePaidMinutes: row.pause_paid_minutes,\n\t};\n}\n\nconst USER_COLUMNS = `id, login, password_hash, display_name, email, rfid_card, is_active,\n\\tmust_change_pw, locale, timezone, pin_hash, avatar, created_at, updated_at, last_login_at`;\n\nconst PROFILE_COLUMNS = `user_id, percent, weekly_hours, workdays, start_date, end_date, overtime_carryover,\n\\tvorholzeit_per_year, vacation_carryover, vacation_per_year, overtime_model, holiday_flags, pause_paid_minutes`;\n\n/** Field names of a user that are compared for the audit trail (the password is reported separately). */\nconst AUDITED_USER_FIELDS: (keyof UserRecord)[] = [\n\t\"login\",\n\t\"displayName\",\n\t\"email\",\n\t\"rfidCard\",\n\t\"isActive\",\n\t\"mustChangePw\",\n\t\"locale\",\n\t\"timezone\",\n];\n\n/** Field names of a work profile that are compared for the audit trail. */\n/** Field names of a work profile that are compared for the audit trail. */\nconst AUDITED_PROFILE_FIELDS: (keyof WorkProfileRecord)[] = [\n\t\"percent\",\n\t\"weeklyHours\",\n\t\"workdays\",\n\t\"startDate\",\n\t\"endDate\",\n\t\"overtimeCarryover\",\n\t\"vorholzeitPerYear\",\n\t\"vacationCarryover\",\n\t\"vacationPerYear\",\n\t\"overtimeModel\",\n\t\"holidayFlags\",\n\t\"pausePaidMinutes\",\n];\n\n/**\n * Creates the user repository.\n *\n * @param db - open database handle\n * @returns repository instance\n */\nexport function createUsersRepository(db: Db): UsersRepository {\n\tconst selectById = db.prepare(`SELECT ${USER_COLUMNS} FROM users WHERE id = ?`);\n\tconst selectByLogin = db.prepare(`SELECT ${USER_COLUMNS} FROM users WHERE login = ? COLLATE NOCASE`);\n\tconst selectByRfid = db.prepare(`SELECT ${USER_COLUMNS} FROM users WHERE rfid_card = ? COLLATE NOCASE LIMIT 1`);\n\tconst setPinHash = db.prepare(\"UPDATE users SET pin_hash = ?, updated_at = ? WHERE id = ?\");\n\tconst setAvatarData = db.prepare(\"UPDATE users SET avatar = ?, updated_at = ? WHERE id = ?\");\n\tconst selectAll = db.prepare(`SELECT ${USER_COLUMNS} FROM users ORDER BY display_name COLLATE NOCASE, id`);\n\tconst selectActive = db.prepare(\n\t\t`SELECT ${USER_COLUMNS} FROM users WHERE is_active = 1 ORDER BY display_name COLLATE NOCASE, id`,\n\t);\n\tconst insertUser = db.prepare(\n\t\t`INSERT INTO users\n\t\t (login, password_hash, display_name, email, rfid_card, is_active, must_change_pw,\n\t\t locale, timezone, created_at, updated_at)\n\t\t VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n\t);\n\tconst updateUser = db.prepare(\n\t\t`UPDATE users SET display_name = ?, email = ?, locale = ?, timezone = ?, rfid_card = ?,\n\t\t is_active = ?, must_change_pw = ?, password_hash = ?, updated_at = ?\n\t\t WHERE id = ?`,\n\t);\n\tconst updateLastLogin = db.prepare(\"UPDATE users SET last_login_at = ? WHERE id = ?\");\n\tconst insertDefaultProfile = db.prepare(\"INSERT OR IGNORE INTO work_profiles (user_id) VALUES (?)\");\n\tconst selectProfile = db.prepare(`SELECT ${PROFILE_COLUMNS} FROM work_profiles WHERE user_id = ?`);\n\tconst upsertProfile = db.prepare(\n\t\t`INSERT INTO work_profiles (${PROFILE_COLUMNS})\n\t\t VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n\t\t ON CONFLICT(user_id) DO UPDATE SET\n\t\t percent = excluded.percent,\n\t\t weekly_hours = excluded.weekly_hours,\n\t\t workdays = excluded.workdays,\n\t\t start_date = excluded.start_date,\n\t\t end_date = excluded.end_date,\n\t\t overtime_carryover = excluded.overtime_carryover,\n\t\t vorholzeit_per_year = excluded.vorholzeit_per_year,\n\t\t vacation_carryover = excluded.vacation_carryover,\n\t\t vacation_per_year = excluded.vacation_per_year,\n\t\t overtime_model = excluded.overtime_model,\n\t\t holiday_flags = excluded.holiday_flags,\n\t\t pause_paid_minutes = excluded.pause_paid_minutes`,\n\t);\n\tconst selectRoles = db.prepare(\n\t\t`SELECT r.key FROM user_roles ur JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = ? ORDER BY r.key`,\n\t);\n\tconst selectRoleId = db.prepare(\"SELECT id FROM roles WHERE key = ?\");\n\tconst deleteRoles = db.prepare(\"DELETE FROM user_roles WHERE user_id = ?\");\n\tconst insertRole = db.prepare(\"INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)\");\n\tconst selectPermissions = db.prepare(\n\t\t`SELECT DISTINCT p.key FROM user_roles ur\n\t\t JOIN role_permissions rp ON rp.role_id = ur.role_id\n\t\t JOIN permissions p ON p.id = rp.permission_id\n\t\t WHERE ur.user_id = ? ORDER BY p.key`,\n\t);\n\tconst selectRoleCatalog = db.prepare(\n\t\t`SELECT r.key AS key, r.name AS name, p.key AS permission\n\t\t FROM roles r\n\t\t LEFT JOIN role_permissions rp ON rp.role_id = r.id\n\t\t LEFT JOIN permissions p ON p.id = rp.permission_id\n\t\t ORDER BY r.key, p.key`,\n\t);\n\n\tconst read = (id: number): UserRecord | null => {\n\t\tconst row = selectById.get(id) as UserRow | undefined;\n\t\treturn row ? mapUserRow(row) : null;\n\t};\n\n\tconst readProfile = (userId: number): WorkProfileRecord | null => {\n\t\tconst row = selectProfile.get(userId) as WorkProfileRow | undefined;\n\t\treturn row ? mapWorkProfileRow(row) : null;\n\t};\n\n\t/**\n\t * Defaults of a fresh work profile (identical to the column defaults of the schema).\n\t *\n\t * @param userId - owner of the profile\n\t */\n\tconst defaultProfile = (userId: number): WorkProfileRecord => ({\n\t\tuserId,\n\t\tpercent: 100,\n\t\tweeklyHours: 42.5,\n\t\tworkdays: \"0;1;1;1;1;1;0\",\n\t\tstartDate: null,\n\t\tendDate: null,\n\t\tovertimeCarryover: 0,\n\t\tvorholzeitPerYear: 0,\n\t\tvacationCarryover: 0,\n\t\tvacationPerYear: 0,\n\t\tovertimeModel: \"monthly\",\n\t\tholidayFlags: null,\n\t\tpausePaidMinutes: 0,\n\t});\n\n\tconst resolveRoleIds = (roleKeys: string[]): number[] =>\n\t\troleKeys.map(key => {\n\t\t\tconst row = selectRoleId.get(key) as { id: number } | undefined;\n\t\t\tif (!row) {\n\t\t\t\tthrow new UnknownRoleError(key);\n\t\t\t}\n\t\t\treturn row.id;\n\t\t});\n\n\tconst writeRoles = (userId: number, roleIds: number[]): void => {\n\t\tdeleteRoles.run(userId);\n\t\tfor (const roleId of roleIds) {\n\t\t\tinsertRole.run(userId, roleId);\n\t\t}\n\t};\n\n\treturn {\n\t\tcreate(input: CreateUserInput): UserRecord {\n\t\t\tconst login = input.login.trim();\n\t\t\tif (!login) {\n\t\t\t\tthrow new ValidationError(\"login must not be empty\");\n\t\t\t}\n\t\t\tconst existing = selectByLogin.get(login) as UserRow | undefined;\n\t\t\tif (existing) {\n\t\t\t\tthrow new LoginExistsError(login);\n\t\t\t}\n\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst roleKeys = input.roleKeys ?? [];\n\t\t\tconst roleIds = resolveRoleIds(roleKeys);\n\n\t\t\tlet userId = 0;\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tconst result = insertUser.run(\n\t\t\t\t\tlogin,\n\t\t\t\t\tinput.passwordHash ?? \"\",\n\t\t\t\t\tinput.displayName,\n\t\t\t\t\tinput.email ?? null,\n\t\t\t\t\tinput.rfidCard ?? null,\n\t\t\t\t\t1,\n\t\t\t\t\tinput.mustChangePw === true ? 1 : 0,\n\t\t\t\t\tinput.locale ?? \"de-DE\",\n\t\t\t\t\tinput.timezone ?? \"Europe/Berlin\",\n\t\t\t\t\tnow,\n\t\t\t\t\tnow,\n\t\t\t\t);\n\t\t\t\tuserId = Number(result.lastInsertRowid);\n\t\t\t\tinsertDefaultProfile.run(userId);\n\t\t\t\twriteRoles(userId, roleIds);\n\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId ?? null,\n\t\t\t\t\taction: \"user.create\",\n\t\t\t\t\tentity: \"user\",\n\t\t\t\t\tentityId: userId,\n\t\t\t\t\tdetail: { login, displayName: input.displayName, roles: roleKeys.slice().sort() },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\n\t\t\tconst created = read(userId);\n\t\t\tif (!created) {\n\t\t\t\tthrow new Error(`user ${userId} disappeared right after creation`);\n\t\t\t}\n\t\t\treturn created;\n\t\t},\n\n\t\tfindById: read,\n\n\t\tfindByLogin(login: string): UserRecord | null {\n\t\t\tconst row = selectByLogin.get(login.trim()) as UserRow | undefined;\n\t\t\treturn row ? mapUserRow(row) : null;\n\t\t},\n\n\t\tfindByRfidCard(card: string): UserRecord | null {\n\t\t\tconst row = selectByRfid.get(card.trim()) as UserRow | undefined;\n\t\t\treturn row ? mapUserRow(row) : null;\n\t\t},\n\n\t\tsetPin(input: {\n\t\t\tuserId: number;\n\t\t\tpinHash: string | null;\n\t\t\tactorId: number;\n\t\t\tactorIp?: string | null;\n\t\t\tnow?: number;\n\t\t}): void {\n\t\t\tif (!read(input.userId)) {\n\t\t\t\tthrow new NotFoundError(`user ${input.userId} not found`);\n\t\t\t}\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tsetPinHash.run(input.pinHash, now, input.userId);\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: \"user.pin\",\n\t\t\t\t\tentity: \"user\",\n\t\t\t\t\tentityId: input.userId,\n\t\t\t\t\t// the hash itself never reaches the audit trail\n\t\t\t\t\tdetail: { cleared: input.pinHash === null },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\t\t},\n\n\t\tsetAvatar(input: {\n\t\t\tuserId: number;\n\t\t\tavatar: string | null;\n\t\t\tactorId: number;\n\t\t\tactorIp?: string | null;\n\t\t\tnow?: number;\n\t\t}): void {\n\t\t\tif (!read(input.userId)) {\n\t\t\t\tthrow new NotFoundError(`user ${input.userId} not found`);\n\t\t\t}\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tsetAvatarData.run(input.avatar, now, input.userId);\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: \"user.avatar\",\n\t\t\t\t\tentity: \"user\",\n\t\t\t\t\tentityId: input.userId,\n\t\t\t\t\t// the picture itself never reaches the audit trail\n\t\t\t\t\tdetail: { cleared: input.avatar === null },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\t\t},\n\n\t\tlist(options?: { includeInactive?: boolean }): UserRecord[] {\n\t\t\tconst rows = (options?.includeInactive === true ? selectAll : selectActive).all() as UserRow[];\n\t\t\treturn rows.map(mapUserRow);\n\t\t},\n\n\t\tupdate(input: UpdateUserInput): UserRecord {\n\t\t\tconst current = read(input.id);\n\t\t\tif (!current) {\n\t\t\t\tthrow new NotFoundError(`user ${input.id} not found`);\n\t\t\t}\n\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst next: UserRecord = {\n\t\t\t\t...current,\n\t\t\t\tdisplayName: input.patch.displayName ?? current.displayName,\n\t\t\t\temail: input.patch.email === undefined ? current.email : input.patch.email,\n\t\t\t\tlocale: input.patch.locale ?? current.locale,\n\t\t\t\ttimezone: input.patch.timezone ?? current.timezone,\n\t\t\t\trfidCard: input.patch.rfidCard === undefined ? current.rfidCard : input.patch.rfidCard,\n\t\t\t\tisActive: input.patch.isActive ?? current.isActive,\n\t\t\t\tmustChangePw: input.patch.mustChangePw ?? current.mustChangePw,\n\t\t\t\tpasswordHash: input.patch.passwordHash ?? current.passwordHash,\n\t\t\t};\n\n\t\t\tconst changes = diffFields(\n\t\t\t\tcurrent as unknown as Record<string, unknown>,\n\t\t\t\tnext as unknown as Record<string, unknown>,\n\t\t\t\tAUDITED_USER_FIELDS as unknown as (keyof Record<string, unknown>)[],\n\t\t\t);\n\t\t\tconst passwordChanged = next.passwordHash !== current.passwordHash;\n\t\t\tif (Object.keys(changes).length === 0 && !passwordChanged) {\n\t\t\t\t// nothing to do, keep `updated_at` untouched\n\t\t\t\treturn current;\n\t\t\t}\n\n\t\t\tconst action =\n\t\t\t\t!current.isActive && next.isActive\n\t\t\t\t\t? \"user.activate\"\n\t\t\t\t\t: current.isActive && !next.isActive\n\t\t\t\t\t\t? \"user.deactivate\"\n\t\t\t\t\t\t: \"user.update\";\n\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tupdateUser.run(\n\t\t\t\t\tnext.displayName,\n\t\t\t\t\tnext.email,\n\t\t\t\t\tnext.locale,\n\t\t\t\t\tnext.timezone,\n\t\t\t\t\tnext.rfidCard,\n\t\t\t\t\tnext.isActive ? 1 : 0,\n\t\t\t\t\tnext.mustChangePw ? 1 : 0,\n\t\t\t\t\tnext.passwordHash,\n\t\t\t\t\tnow,\n\t\t\t\t\tinput.id,\n\t\t\t\t);\n\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction,\n\t\t\t\t\tentity: \"user\",\n\t\t\t\t\tentityId: input.id,\n\t\t\t\t\t// never write the password hash into the audit trail\n\t\t\t\t\tdetail: { changes, passwordChanged, reason: input.reason ?? null },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\n\t\t\tconst updated = read(input.id);\n\t\t\tif (!updated) {\n\t\t\t\tthrow new Error(`user ${input.id} disappeared right after the update`);\n\t\t\t}\n\t\t\treturn updated;\n\t\t},\n\n\t\troles(userId: number): string[] {\n\t\t\treturn (selectRoles.all(userId) as { key: string }[]).map(row => row.key);\n\t\t},\n\n\t\troleCatalog(): { key: string; name: string; permissions: string[] }[] {\n\t\t\tconst rows = selectRoleCatalog.all() as { key: string; name: string; permission: string | null }[];\n\t\t\tconst catalog = new Map<string, { key: string; name: string; permissions: string[] }>();\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst entry = catalog.get(row.key) ?? { key: row.key, name: row.name, permissions: [] };\n\t\t\t\tif (row.permission) {\n\t\t\t\t\tentry.permissions.push(row.permission);\n\t\t\t\t}\n\t\t\t\tcatalog.set(row.key, entry);\n\t\t\t}\n\t\t\treturn [...catalog.values()];\n\t\t},\n\n\t\tsetRoles(input: {\n\t\t\tuserId: number;\n\t\t\troleKeys: string[];\n\t\t\tactorId: number;\n\t\t\tactorIp?: string | null;\n\t\t\tnow?: number;\n\t\t}): string[] {\n\t\t\tif (!read(input.userId)) {\n\t\t\t\tthrow new NotFoundError(`user ${input.userId} not found`);\n\t\t\t}\n\t\t\t// resolve (and validate) all keys before the first write\n\t\t\tconst roleIds = resolveRoleIds(input.roleKeys);\n\t\t\tconst before = this.roles(input.userId);\n\t\t\tconst after = input.roleKeys.slice().sort();\n\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\twriteRoles(input.userId, roleIds);\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: \"user.roles\",\n\t\t\t\t\tentity: \"user\",\n\t\t\t\t\tentityId: input.userId,\n\t\t\t\t\tdetail: { old: before, new: after },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\n\t\t\treturn this.roles(input.userId);\n\t\t},\n\n\t\tpermissions(userId: number): string[] {\n\t\t\treturn (selectPermissions.all(userId) as { key: string }[]).map(row => row.key);\n\t\t},\n\n\t\thasPermissions(userId: number, required: string[]): boolean {\n\t\t\tconst granted = new Set(this.permissions(userId));\n\t\t\treturn required.every(permission => granted.has(permission));\n\t\t},\n\n\t\tgetWorkProfile: readProfile,\n\n\t\tsaveWorkProfile(input: {\n\t\t\tuserId: number;\n\t\t\tprofile: Partial<Omit<WorkProfileRecord, \"userId\">>;\n\t\t\tactorId: number;\n\t\t\tactorIp?: string | null;\n\t\t\tnow?: number;\n\t\t}): WorkProfileRecord {\n\t\t\tconst current = readProfile(input.userId);\n\t\t\tconst base = current ?? defaultProfile(input.userId);\n\t\t\tconst next: WorkProfileRecord = { ...base, ...input.profile, userId: input.userId };\n\n\t\t\tconst changes = diffFields(\n\t\t\t\tbase as unknown as Record<string, unknown>,\n\t\t\t\tnext as unknown as Record<string, unknown>,\n\t\t\t\tAUDITED_PROFILE_FIELDS as unknown as (keyof Record<string, unknown>)[],\n\t\t\t);\n\t\t\tif (current && Object.keys(changes).length === 0) {\n\t\t\t\treturn current;\n\t\t\t}\n\n\t\t\tconst now = input.now ?? Math.floor(Date.now() / 1000);\n\t\t\tconst run = db.transaction((): void => {\n\t\t\t\tupsertProfile.run(\n\t\t\t\t\tnext.userId,\n\t\t\t\t\tnext.percent,\n\t\t\t\t\tnext.weeklyHours,\n\t\t\t\t\tnext.workdays,\n\t\t\t\t\tnext.startDate,\n\t\t\t\t\tnext.endDate,\n\t\t\t\t\tnext.overtimeCarryover,\n\t\t\t\t\tnext.vorholzeitPerYear,\n\t\t\t\t\tnext.vacationCarryover,\n\t\t\t\t\tnext.vacationPerYear,\n\t\t\t\t\tnext.overtimeModel,\n\t\t\t\t\tnext.holidayFlags,\n\t\t\t\t\tnext.pausePaidMinutes,\n\t\t\t\t);\n\n\t\t\t\twriteAuditLog(db, {\n\t\t\t\t\tatUtc: now,\n\t\t\t\t\tactorId: input.actorId,\n\t\t\t\t\taction: \"user.profile\",\n\t\t\t\t\tentity: \"work_profile\",\n\t\t\t\t\tentityId: input.userId,\n\t\t\t\t\tdetail: { created: current === null, changes },\n\t\t\t\t\tip: input.actorIp ?? null,\n\t\t\t\t});\n\t\t\t});\n\t\t\trun();\n\n\t\t\tconst saved = readProfile(input.userId);\n\t\t\tif (!saved) {\n\t\t\t\tthrow new Error(`work profile of user ${input.userId} disappeared right after the update`);\n\t\t\t}\n\t\t\treturn saved;\n\t\t},\n\n\t\trecordLogin(userId: number, now?: number): void {\n\t\t\tupdateLastLogin.run(now ?? Math.floor(Date.now() / 1000), userId);\n\t\t},\n\t};\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["_a", "_b"]
|
|
7
|
+
}
|