iobroker.time-tracker 0.7.0 → 0.7.1
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/README.md +48 -18
- package/admin/i18n/de.json +1 -1
- package/admin/i18n/en.json +1 -1
- package/admin/i18n/es.json +1 -1
- package/admin/i18n/fr.json +1 -1
- package/admin/i18n/it.json +1 -1
- package/admin/i18n/nl.json +1 -1
- package/admin/i18n/pl.json +1 -1
- package/admin/i18n/pt.json +1 -1
- package/admin/i18n/ru.json +1 -1
- package/admin/i18n/uk.json +1 -1
- package/admin/i18n/zh-cn.json +1 -1
- package/admin/jsonConfig.json +1 -1
- package/build/lib/db/migrations.js +39 -0
- package/build/lib/db/migrations.js.map +2 -2
- package/build/lib/db/repositories/dayNotes.js +183 -0
- package/build/lib/db/repositories/dayNotes.js.map +7 -0
- package/build/lib/db/seed.js +1 -1
- package/build/lib/db/seed.js.map +2 -2
- package/build/lib/services/sync.js +11 -9
- package/build/lib/services/sync.js.map +2 -2
- package/build/lib/web/api.js +88 -12
- package/build/lib/web/api.js.map +2 -2
- package/build/lib/web/events.js.map +2 -2
- package/build/main.js +4 -0
- package/build/main.js.map +2 -2
- package/io-package.json +14 -14
- package/package.json +1 -1
- package/src-pwa/src/i18n/de.json +11 -1
- package/src-pwa/src/i18n/en.json +11 -1
- package/src-pwa/src/i18n/es.json +11 -1
- package/src-pwa/src/i18n/fr.json +11 -1
- package/src-pwa/src/i18n/it.json +11 -1
- package/src-pwa/src/i18n/nl.json +11 -1
- package/src-pwa/src/i18n/pl.json +11 -1
- package/src-pwa/src/i18n/pt.json +11 -1
- package/src-pwa/src/i18n/ru.json +11 -1
- package/src-pwa/src/i18n/uk.json +11 -1
- package/src-pwa/src/i18n/zh-cn.json +11 -1
- package/www/assets/index-D5fSC8CA.js +119 -0
- package/www/assets/index-D5fSC8CA.js.map +1 -0
- package/www/index.html +1 -1
- package/www/sw.js +1 -1
- package/www/sw.js.map +1 -1
- package/www/assets/index-DIM5i7zh.js +0 -119
- package/www/assets/index-DIM5i7zh.js.map +0 -1
package/build/lib/web/api.js
CHANGED
|
@@ -510,6 +510,21 @@ function createApi(deps) {
|
|
|
510
510
|
const raw = context.query("userId");
|
|
511
511
|
return resolveScope(context, raw ? Number(raw) : null, ownPermission, otherPermission);
|
|
512
512
|
};
|
|
513
|
+
const dayNoteTarget = (context, actorId) => {
|
|
514
|
+
var _a2, _b2;
|
|
515
|
+
const raw = context.query("userId");
|
|
516
|
+
const target = raw === null || raw.trim() === "" ? actorId : Number(raw);
|
|
517
|
+
if (!Number.isInteger(target)) {
|
|
518
|
+
throw new import_errors.ValidationError("userId must be a whole number");
|
|
519
|
+
}
|
|
520
|
+
if (target !== actorId && !((_b2 = (_a2 = context.auth) == null ? void 0 : _a2.permissions.includes("time.edit_other")) != null ? _b2 : false)) {
|
|
521
|
+
throw (0, import_problem.problem)(403, "permission_denied", "request rejected (permission_denied: time.edit_other)");
|
|
522
|
+
}
|
|
523
|
+
if (!users.findById(target)) {
|
|
524
|
+
throw new import_errors.NotFoundError(`user ${target} not found`);
|
|
525
|
+
}
|
|
526
|
+
return target;
|
|
527
|
+
};
|
|
513
528
|
const numberQuery = (context, name) => {
|
|
514
529
|
const raw = context.query(name);
|
|
515
530
|
const value = Number(raw);
|
|
@@ -612,6 +627,9 @@ function createApi(deps) {
|
|
|
612
627
|
const result = sync.sync({
|
|
613
628
|
userId: user.id,
|
|
614
629
|
timeZone: user.timezone,
|
|
630
|
+
// the offline queue is the only way an employee still writes a punch of his own, so the window of the
|
|
631
|
+
// instance applies to it as well (`0` switches the bound off)
|
|
632
|
+
maxPastSeconds: Math.max(0, settings.getNumber("edit_window_days", 0)) * 86400,
|
|
615
633
|
punches: body.punches.map((raw) => {
|
|
616
634
|
const punch2 = raw != null ? raw : {};
|
|
617
635
|
const tsUtc = optionalNumber(punch2, "tsUtc");
|
|
@@ -648,10 +666,13 @@ function createApi(deps) {
|
|
|
648
666
|
"GET",
|
|
649
667
|
"/entries/conflicts",
|
|
650
668
|
{ permission: "time.resolve_conflict" },
|
|
651
|
-
(context) =>
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
669
|
+
(context) => (
|
|
670
|
+
// without `?userId=` the own queue; the administration reads the queue of an employee with it, because a
|
|
671
|
+
// conflict of an employee waits for a decision of the office
|
|
672
|
+
(0, import_router.json)(200, {
|
|
673
|
+
conflicts: sync.conflicts(scopeUser(context, "time.resolve_conflict", "time.resolve_conflict"))
|
|
674
|
+
})
|
|
675
|
+
)
|
|
655
676
|
);
|
|
656
677
|
route("POST", "/entries/:id/resolve", { permission: "time.resolve_conflict", csrf: true }, (context) => {
|
|
657
678
|
var _a2, _b2;
|
|
@@ -732,7 +753,7 @@ function createApi(deps) {
|
|
|
732
753
|
}
|
|
733
754
|
return (0, import_router.json)(200, { entries: entries.listByRange(userId, from, to) });
|
|
734
755
|
});
|
|
735
|
-
route("POST", "/entries", { permission: "time.
|
|
756
|
+
route("POST", "/entries", { permission: "time.edit_other", csrf: true }, (context) => {
|
|
736
757
|
var _a2, _b2;
|
|
737
758
|
if (!context.auth) {
|
|
738
759
|
throw (0, import_problem.problem)(401, "no_session", "request rejected (no_session)");
|
|
@@ -742,10 +763,6 @@ function createApi(deps) {
|
|
|
742
763
|
if (!Number.isInteger(target)) {
|
|
743
764
|
throw new import_errors.ValidationError("userId must be a whole number");
|
|
744
765
|
}
|
|
745
|
-
const own = target === context.auth.user.id;
|
|
746
|
-
if (!own && !context.auth.permissions.includes("time.edit_other")) {
|
|
747
|
-
throw (0, import_problem.problem)(403, "permission_denied", "request rejected (permission_denied: time.edit_other)");
|
|
748
|
-
}
|
|
749
766
|
const user = users.findById(target);
|
|
750
767
|
if (!user) {
|
|
751
768
|
throw new import_errors.NotFoundError(`user ${target} not found`);
|
|
@@ -761,7 +778,8 @@ function createApi(deps) {
|
|
|
761
778
|
tsUtc,
|
|
762
779
|
clientTsUtc: optionalNumber(body, "clientTsUtc"),
|
|
763
780
|
timeZone: user.timezone,
|
|
764
|
-
|
|
781
|
+
// a hand-written punch is always a correction; the normal punch comes through `POST /punch` (`web`)
|
|
782
|
+
source: "admin",
|
|
765
783
|
direction: optionalDirection(body),
|
|
766
784
|
idempotencyKey: optionalString(body, "idempotencyKey"),
|
|
767
785
|
note: optionalString(body, "note"),
|
|
@@ -778,7 +796,7 @@ function createApi(deps) {
|
|
|
778
796
|
entryId: stored.entry.id,
|
|
779
797
|
created: stored.created,
|
|
780
798
|
localDate: stored.entry.localDate,
|
|
781
|
-
source:
|
|
799
|
+
source: "admin"
|
|
782
800
|
}
|
|
783
801
|
});
|
|
784
802
|
return (0, import_router.json)(
|
|
@@ -1475,9 +1493,12 @@ function createApi(deps) {
|
|
|
1475
1493
|
if (revision === null) {
|
|
1476
1494
|
throw new import_errors.ValidationError("revision is required");
|
|
1477
1495
|
}
|
|
1478
|
-
requireInsideEditWindow(context, existing.userId, existing.tsUtc);
|
|
1479
1496
|
const wantedTs = optionalNumber(body, "tsUtc");
|
|
1497
|
+
if (wantedTs !== null && !context.auth.permissions.includes("time.edit_other")) {
|
|
1498
|
+
throw (0, import_problem.problem)(403, "permission_denied", "request rejected (permission_denied: time.edit_other)");
|
|
1499
|
+
}
|
|
1480
1500
|
if (wantedTs !== null) {
|
|
1501
|
+
requireInsideEditWindow(context, existing.userId, existing.tsUtc);
|
|
1481
1502
|
requireInsideEditWindow(context, existing.userId, wantedTs);
|
|
1482
1503
|
}
|
|
1483
1504
|
const target = users.findById(existing.userId);
|
|
@@ -1530,6 +1551,61 @@ function createApi(deps) {
|
|
|
1530
1551
|
});
|
|
1531
1552
|
return (0, import_router.noContent)();
|
|
1532
1553
|
});
|
|
1554
|
+
route("GET", "/day-notes", { permission: "report.view_own" }, (context) => {
|
|
1555
|
+
var _a2;
|
|
1556
|
+
const userId = scopeUser(context);
|
|
1557
|
+
const from = context.query("from");
|
|
1558
|
+
const to = (_a2 = context.query("to")) != null ? _a2 : from;
|
|
1559
|
+
if (!from || !to) {
|
|
1560
|
+
throw new import_errors.ValidationError("from and to are required");
|
|
1561
|
+
}
|
|
1562
|
+
return (0, import_router.json)(200, { notes: deps.dayNotes.listByRange(userId, from, to) });
|
|
1563
|
+
});
|
|
1564
|
+
route("PUT", "/day-notes", { permission: "time.edit_own", csrf: true }, (context) => {
|
|
1565
|
+
var _a2, _b2;
|
|
1566
|
+
if (!context.auth) {
|
|
1567
|
+
throw (0, import_problem.problem)(401, "no_session", "request rejected (no_session)");
|
|
1568
|
+
}
|
|
1569
|
+
const body = context.jsonBody();
|
|
1570
|
+
const userId = dayNoteTarget(context, context.auth.user.id);
|
|
1571
|
+
const localDate2 = context.query("date");
|
|
1572
|
+
if (!localDate2) {
|
|
1573
|
+
throw new import_errors.ValidationError("date is required");
|
|
1574
|
+
}
|
|
1575
|
+
const note = deps.dayNotes.save({
|
|
1576
|
+
userId,
|
|
1577
|
+
localDate: localDate2,
|
|
1578
|
+
// an empty text is how a note is taken back
|
|
1579
|
+
note: (_a2 = optionalString(body, "note")) != null ? _a2 : "",
|
|
1580
|
+
actorId: context.auth.user.id,
|
|
1581
|
+
actorIp: (_b2 = context.request.remoteAddress) != null ? _b2 : null,
|
|
1582
|
+
now: now()
|
|
1583
|
+
});
|
|
1584
|
+
emit({ type: "dayNote.change", userId, data: { localDate: localDate2 } });
|
|
1585
|
+
return (0, import_router.json)(200, { note });
|
|
1586
|
+
});
|
|
1587
|
+
route("POST", "/day-notes/handled", { permission: "time.edit_other", csrf: true }, (context) => {
|
|
1588
|
+
var _a2, _b2;
|
|
1589
|
+
if (!context.auth) {
|
|
1590
|
+
throw (0, import_problem.problem)(401, "no_session", "request rejected (no_session)");
|
|
1591
|
+
}
|
|
1592
|
+
const userId = dayNoteTarget(context, context.auth.user.id);
|
|
1593
|
+
const localDate2 = context.query("date");
|
|
1594
|
+
if (!localDate2) {
|
|
1595
|
+
throw new import_errors.ValidationError("date is required");
|
|
1596
|
+
}
|
|
1597
|
+
const handled = (_a2 = optionalBoolean(context.jsonBody(), "handled")) != null ? _a2 : true;
|
|
1598
|
+
const note = deps.dayNotes.setHandled({
|
|
1599
|
+
userId,
|
|
1600
|
+
localDate: localDate2,
|
|
1601
|
+
handled,
|
|
1602
|
+
actorId: context.auth.user.id,
|
|
1603
|
+
actorIp: (_b2 = context.request.remoteAddress) != null ? _b2 : null,
|
|
1604
|
+
now: now()
|
|
1605
|
+
});
|
|
1606
|
+
emit({ type: "dayNote.change", userId, data: { localDate: localDate2 } });
|
|
1607
|
+
return (0, import_router.json)(200, { note });
|
|
1608
|
+
});
|
|
1533
1609
|
route("GET", "/entries/:id/audit", { permission: "audit.view" }, (context) => {
|
|
1534
1610
|
const entryId = numberParam(context, "id");
|
|
1535
1611
|
if (!entries.findById(entryId)) {
|