@underpostnet/underpost 2.96.0 → 2.97.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.
Files changed (46) hide show
  1. package/.dockerignore +1 -2
  2. package/.env.development +0 -3
  3. package/.env.production +0 -3
  4. package/.env.test +0 -3
  5. package/.prettierignore +1 -2
  6. package/README.md +31 -31
  7. package/baremetal/commission-workflows.json +64 -17
  8. package/baremetal/packer-workflows.json +11 -0
  9. package/cli.md +72 -40
  10. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  11. package/manifests/deployment/dd-test-development/deployment.yaml +4 -4
  12. package/package.json +3 -2
  13. package/packer/images/Rocky9Amd64/rocky9.pkr.hcl +6 -2
  14. package/packer/images/Rocky9Arm64/Makefile +69 -0
  15. package/packer/images/Rocky9Arm64/README.md +122 -0
  16. package/packer/images/Rocky9Arm64/http/rocky9.ks.pkrtpl.hcl +114 -0
  17. package/packer/images/Rocky9Arm64/rocky9.pkr.hcl +171 -0
  18. package/scripts/disk-clean.sh +128 -187
  19. package/scripts/ipxe-setup.sh +197 -0
  20. package/scripts/packer-init-vars-file.sh +16 -6
  21. package/scripts/packer-setup.sh +270 -33
  22. package/scripts/ports-ls.sh +31 -0
  23. package/scripts/quick-tftp.sh +19 -0
  24. package/src/api/document/document.controller.js +15 -0
  25. package/src/api/document/document.model.js +14 -0
  26. package/src/api/document/document.router.js +1 -0
  27. package/src/api/document/document.service.js +61 -3
  28. package/src/cli/baremetal.js +1716 -439
  29. package/src/cli/cloud-init.js +354 -231
  30. package/src/cli/cluster.js +1 -1
  31. package/src/cli/db.js +22 -0
  32. package/src/cli/deploy.js +6 -2
  33. package/src/cli/image.js +1 -0
  34. package/src/cli/index.js +40 -36
  35. package/src/cli/run.js +77 -11
  36. package/src/cli/ssh.js +1 -1
  37. package/src/client/components/core/Input.js +3 -1
  38. package/src/client/components/core/Panel.js +161 -15
  39. package/src/client/components/core/PanelForm.js +198 -35
  40. package/src/client/components/core/Translate.js +11 -0
  41. package/src/client/services/document/document.service.js +19 -0
  42. package/src/index.js +2 -1
  43. package/src/server/dns.js +8 -2
  44. package/src/server/start.js +14 -6
  45. package/manifests/mariadb/config.yaml +0 -10
  46. package/manifests/mariadb/secret.yaml +0 -8
@@ -1,52 +1,289 @@
1
1
  #!/usr/bin/env bash
2
- # Script to install Packer and libvirt/qemu tooling on Rocky Linux.
3
- # Usage: sudo ./scripts/packer-setup.sh
2
+ # packer-setup.sh
3
+ # RHEL/CentOS/Rocky helper to prepare a host for building both amd64 and aarch64 Packer images.
4
+ # Installs packer (repo fallback), libvirt/qemu tooling, NBD/filesystem tools, UEFI firmware for x86_64 and aarch64,
5
+ # registers qemu-user binfmt for cross-chroot use and compiles qemu-system-aarch64 when repo packages are missing.
6
+ # Usage: sudo ./packer-setup.sh
4
7
 
5
8
  set -euo pipefail
6
9
 
10
+ print(){ printf "[setup] %s\n" "$*"; }
11
+ err(){ printf "[setup] ERROR: %s\n" "$*" >&2; }
7
12
 
8
- # 1) Replace/add the correct HashiCorp repo for RHEL/Rocky
9
- sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
13
+ if [[ $(id -u) -ne 0 ]]; then
14
+ err "This script requires root. Run with sudo."; exit 3
15
+ fi
16
+
17
+ # Detect host arch
18
+ HOST_RAW_ARCH=$(uname -m)
19
+ case "$HOST_RAW_ARCH" in
20
+ x86_64) HOST_ARCH=amd64;;
21
+ aarch64) HOST_ARCH=arm64;;
22
+ *) HOST_ARCH="$HOST_RAW_ARCH";;
23
+ esac
24
+ print "Host architecture: $HOST_RAW_ARCH -> normalized: $HOST_ARCH"
25
+
26
+ # Ensure RHEL-family
27
+ if [[ -f /etc/os-release ]]; then
28
+ . /etc/os-release
29
+ ID_LC=${ID,,}
30
+ ID_LIKE=${ID_LIKE,,}
31
+ else
32
+ ID_LC=unknown; ID_LIKE=unknown
33
+ fi
34
+ if [[ $ID_LC != "rocky" && $ID_LC != "rhel" && $ID_LC != "centos" && $ID_LIKE != *"rhel"* ]]; then
35
+ err "This script targets RHEL/CentOS/Rocky family. Detected: $ID_LC (like: $ID_LIKE). Exiting."; exit 4
36
+ fi
37
+ print "Distro detected: $PRETTY_NAME"
38
+
39
+ # Enable helpful repos and install helpers
40
+ print "Installing dnf-plugins-core and enabling CRB/PowerTools (if available)"
41
+ set +e
42
+ dnf install -y dnf-plugins-core >/dev/null 2>&1 || true
43
+ dnf config-manager --set-enabled crb >/dev/null 2>&1 || true
44
+ dnf config-manager --set-enabled powertools >/dev/null 2>&1 || true
45
+ # EPEL
46
+ if ! rpm -q epel-release >/dev/null 2>&1; then
47
+ print "Installing epel-release"
48
+ dnf install -y epel-release || true
49
+ fi
50
+ set -e
51
+
52
+ # 1) Try to install packer from distro repos, otherwise add HashiCorp repo and install
53
+ print "Attempting to install packer from distro repos"
54
+ if dnf install -y packer >/dev/null 2>&1; then
55
+ print "Packer installed from distro repo"
56
+ else
57
+ print "packer not available in distro repos or install failed. Adding HashiCorp repo and retrying."
58
+ # HashiCorp RPM repo for RHEL/CentOS/Rocky family (works for EL8/EL9)
59
+ if ! rpm -q hashicorp >/dev/null 2>&1; then
60
+ cat >/etc/yum.repos.d/hashicorp.repo <<'EOF'
61
+ [hashicorp]
62
+ name=HashiCorp Stable - $basearch
63
+ baseurl=https://rpm.releases.hashicorp.com/RHEL/$releasever/$basearch/stable
64
+ enabled=1
65
+ gpgcheck=1
66
+ gpgkey=https://rpm.releases.hashicorp.com/gpg
67
+ EOF
68
+ fi
69
+ if dnf makecache >/dev/null 2>&1 && dnf install -y packer >/dev/null 2>&1; then
70
+ print "Packer installed from HashiCorp repo"
71
+ else
72
+ err "Packer install from repo failed. You can install packer manually from HashiCorp releases if needed.";
73
+ fi
74
+ fi
10
75
 
11
- # Refresh dnf metadata
12
- sudo dnf clean all
13
- sudo dnf makecache --refresh
76
+ # 2) Install libvirt, qemu tooling and enable libvirtd
77
+ print "Installing libvirt, qemu and related tooling (best-effort)"
78
+ LIBVIRT_PKGS=(libvirt libvirt-daemon qemu-kvm qemu-img virt-install bridge-utils)
79
+ # attempt to include qemu-system-aarch64 and qemu-system-x86_64 if available
80
+ LIBVIRT_PKGS+=(qemu-system-aarch64 qemu-system-x86_64 qemu-system-arm)
14
81
 
15
- # 2) Try to install packer from the repo
16
- sudo dnf install -y packer || echo "packer install from repo failed, try manual install (see below)"
82
+ # Some packages may not exist exactly with those names; install best-effort
83
+ for pkg in "${LIBVIRT_PKGS[@]}"; do
84
+ dnf install -y "$pkg" >/dev/null 2>&1 || print "Package $pkg not available via dnf (skipping)"
85
+ done
17
86
 
18
- # 3) Install libvirt/qemu tooling then start the service
19
- sudo dnf install -y libvirt libvirt-daemon qemu-kvm qemu-img virt-install bridge-utils
20
- sudo systemctl enable --now libvirtd
21
- sudo systemctl status libvirtd --no-pager
87
+ print "Enabling and starting libvirtd"
88
+ systemctl enable --now libvirtd || err "Failed to enable/start libvirtd"
89
+ systemctl status libvirtd --no-pager || true
22
90
 
23
- # 3a) Install NBD and filesystem tools required for MAAS image creation
24
- sudo dnf install -y libnbd nbdkit e2fsprogs kmod-kvdo kmod
91
+ # 3) Install NBD and filesystem tools required for MAAS image creation
92
+ print "Installing NBD and filesystem tooling: libnbd, nbdkit, e2fsprogs, kmod packages (best-effort)"
93
+ NBDS=(libnbd nbdkit e2fsprogs kmod-kvdo kmod)
94
+ for pkg in "${NBDS[@]}"; do
95
+ dnf install -y "$pkg" >/dev/null 2>&1 || print "Package $pkg not available via dnf (skipping)"
96
+ done
25
97
 
26
98
  # 4) Install UEFI firmware for x86_64 and ARM64
27
- sudo dnf install -y edk2-ovmf edk2-aarch64
99
+ print "Installing edk2 / OVMF UEFI firmware packages (x86_64 and aarch64)"
100
+ UEFI_PKGS=(edk2-ovmf edk2-aarch64 edk2-ovmf-aarch64)
101
+ for pkg in "${UEFI_PKGS[@]}"; do
102
+ dnf install -y "$pkg" >/dev/null 2>&1 || print "UEFI package $pkg not available (skipping)"
103
+ done
28
104
 
29
- # 5) Create symlinks for qemu-system-* binaries (Rocky/RHEL uses qemu-kvm instead)
30
- # Packer expects standard qemu-system-* names, but RHEL-based distros use qemu-kvm
31
- if [ -f /usr/libexec/qemu-kvm ] && [ ! -f /usr/bin/qemu-system-x86_64 ]; then
32
- echo "Creating symlink: /usr/bin/qemu-system-x86_64 -> /usr/libexec/qemu-kvm"
33
- sudo ln -sf /usr/libexec/qemu-kvm /usr/bin/qemu-system-x86_64
105
+ # 5) Ensure qemu-user-static for chroot/debootstrap cross-execution and register binfmt via podman
106
+ print "Installing qemu-user-static and registering binfmt handlers via podman"
107
+ if ! rpm -q qemu-user-static >/dev/null 2>&1; then
108
+ dnf install -y qemu-user-static >/dev/null 2>&1 || print "qemu-user-static not in repos (will extract from container)"
34
109
  fi
35
110
 
36
- if [ -f /usr/libexec/qemu-kvm ] && [ ! -f /usr/bin/qemu-system-aarch64 ]; then
37
- echo "Creating symlink: /usr/bin/qemu-system-aarch64 -> /usr/libexec/qemu-kvm"
38
- sudo ln -sf /usr/libexec/qemu-kvm /usr/bin/qemu-system-aarch64
111
+ if command -v podman >/dev/null 2>&1; then
112
+ # Register binfmt handlers
113
+ podman run --rm --privileged multiarch/qemu-user-static --reset -p yes || print "podman run for qemu-user-static failed (skip)"
114
+
115
+ # Extract static binaries to /usr/bin if not already present from RPM
116
+ if ! command -v qemu-aarch64-static >/dev/null 2>&1; then
117
+ print "Extracting qemu static binaries from multiarch/qemu-user-static container"
118
+ CONTAINER_ID=$(podman create multiarch/qemu-user-static:latest)
119
+
120
+ # Extract the binaries we need
121
+ for arch in aarch64 arm armeb; do
122
+ if podman cp "$CONTAINER_ID:/usr/bin/qemu-${arch}-static" "/usr/bin/qemu-${arch}-static" 2>/dev/null; then
123
+ chmod +x "/usr/bin/qemu-${arch}-static"
124
+ print "Installed qemu-${arch}-static to /usr/bin/"
125
+ fi
126
+ done
127
+
128
+ podman rm "$CONTAINER_ID" >/dev/null 2>&1 || true
129
+ fi
130
+ else
131
+ print "podman not available. Install podman to register binfmt for container/chroot convenience."
39
132
  fi
40
133
 
41
- # 6) Create symlinks for OVMF/AAVMF firmware files in expected locations
42
- # Rocky/RHEL stores OVMF in /usr/share/edk2/ovmf, but Packer expects /usr/share/OVMF
43
- if [ -f /usr/share/edk2/ovmf/OVMF_CODE.fd ] && [ ! -f /usr/share/OVMF/OVMF_CODE.fd ]; then
44
- echo "Creating symlink: /usr/share/OVMF/OVMF_CODE.fd -> /usr/share/edk2/ovmf/OVMF_CODE.fd"
45
- sudo ln -sf /usr/share/edk2/ovmf/OVMF_CODE.fd /usr/share/OVMF/OVMF_CODE.fd
134
+ # 6) Check qemu-system-aarch64 availability and 'virt' machine support; offer compile if missing
135
+ check_qemu_system_aarch64(){
136
+ # Explicitly check /usr/local/bin first (where compiled QEMU installs)
137
+ if [ -x /usr/local/bin/qemu-system-aarch64 ]; then
138
+ QBIN=/usr/local/bin/qemu-system-aarch64
139
+ elif command -v qemu-system-aarch64 >/dev/null 2>&1; then
140
+ QBIN=$(command -v qemu-system-aarch64)
141
+ else
142
+ return 1
143
+ fi
144
+
145
+ print "qemu-system-aarch64 found at $QBIN"
146
+ if ! $QBIN -machine help 2>/dev/null | grep -q '\bvirt\b'; then
147
+ err "qemu-system-aarch64 present but 'virt' not listed -> may be missing aarch64 softmmu"
148
+ return 2
149
+ fi
150
+
151
+ # Check for user networking (slirp) support.
152
+ # We specify -machine virt to avoid "No machine specified" errors on some QEMU versions.
153
+ if ! $QBIN -machine virt -netdev help 2>&1 | grep -q '\buser\b'; then
154
+ err "qemu-system-aarch64 present but 'user' network backend not listed -> missing libslirp support"
155
+ return 3
156
+ fi
157
+
158
+ print "qemu-system-aarch64 supports 'virt' and 'user' network -> good for full-system ARM emulation"
159
+ return 0
160
+ }
161
+
162
+ if check_qemu_system_aarch64; then
163
+ print "qemu-system-aarch64 is ready"
164
+ else
165
+ rc=$?
166
+ if [[ $rc -eq 1 ]]; then
167
+ print "qemu-system-aarch64 not found in installed packages"
168
+ else
169
+ print "qemu-system-aarch64 present but incomplete"
170
+ fi
171
+
172
+ # Try install from repo explicitly
173
+ print "Attempting to install qemu-system-aarch64 via dnf (best-effort)"
174
+ if dnf install -y qemu-system-aarch64 >/dev/null 2>&1; then
175
+ print "Installed qemu-system-aarch64 from repo"
176
+ else
177
+ print "qemu-system-aarch64 not available in enabled repos."
178
+ fi
179
+
180
+ if check_qemu_system_aarch64; then
181
+ print "qemu-system-aarch64 now available after package install"
182
+ else
183
+ print "Compiling QEMU with aarch64-softmmu target. Installing build deps..."
184
+ dnf groupinstall -y 'Development Tools' || true
185
+ dnf install -y git libaio-devel libgcrypt-devel libfdt-devel glib2-devel zlib-devel pixman-devel libseccomp-devel libusb1-devel openssl-devel bison flex python3 python3-pip ninja-build || true
186
+
187
+ # Enforce libslirp-devel for user networking
188
+ if ! dnf install -y libslirp-devel; then
189
+ err "Failed to install libslirp-devel. User networking will not work."
190
+ exit 1
191
+ fi
192
+
193
+ # Install required Python packages for QEMU build
194
+ print "Installing Python dependencies for QEMU build"
195
+ python3 -m pip install --upgrade pip || true
196
+ python3 -m pip install tomli meson || true
197
+
198
+ TMPDIR=$(mktemp -d)
199
+ print "Cloning QEMU source to $TMPDIR/qemu"
200
+ # Use a stable release tag (v9.0.0) to ensure consistency
201
+ git clone --depth 1 --branch v9.0.0 https://gitlab.com/qemu-project/qemu.git "$TMPDIR/qemu"
202
+ cd "$TMPDIR/qemu"
203
+
204
+ print "Configuring QEMU build"
205
+ if ./configure --target-list=aarch64-softmmu --enable-virtfs --enable-slirp; then
206
+ print "Configure successful, building QEMU..."
207
+ if make -j"$(nproc)"; then
208
+ print "Build successful, installing..."
209
+ make install || err "QEMU install failed"
210
+ # Update PATH to include /usr/local/bin where QEMU was installed
211
+ export PATH="/usr/local/bin:$PATH"
212
+ hash -r || true
213
+ else
214
+ err "QEMU build (make) failed"
215
+ fi
216
+ else
217
+ err "QEMU configure failed. Check dependencies."
218
+ fi
219
+
220
+ if check_qemu_system_aarch64; then
221
+ print "Successfully compiled and installed qemu-system-aarch64"
222
+ else
223
+ err "Compiled QEMU but qemu-system-aarch64 still missing or lacks 'virt'. Check logs in $TMPDIR/qemu"
224
+ fi
225
+
226
+ cd /
227
+ rm -rf "$TMPDIR" || true
228
+ print "Removed build directory $TMPDIR"
229
+ fi
46
230
  fi
47
231
 
48
- # Create AAVMF symlinks for ARM64 support
49
- if [ -d /usr/share/edk2/aarch64 ] && [ ! -d /usr/share/AAVMF ]; then
50
- echo "Creating symlink: /usr/share/AAVMF -> /usr/share/edk2/aarch64"
51
- sudo ln -sf /usr/share/edk2/aarch64 /usr/share/AAVMF
232
+ # 7) Summary and verification commands for the user
233
+ print "\n=== Summary / Quick verification commands ==="
234
+ if command -v packer >/dev/null 2>&1; then print "packer: $(command -v packer)"; else print "packer: NOT INSTALLED"; fi
235
+ # Check /usr/local/bin explicitly for compiled qemu
236
+ if [ -x /usr/local/bin/qemu-system-aarch64 ]; then
237
+ print "qemu-system-aarch64: /usr/local/bin/qemu-system-aarch64"
238
+ elif command -v qemu-system-aarch64 >/dev/null 2>&1; then
239
+ print "qemu-system-aarch64: $(command -v qemu-system-aarch64)"
240
+ else
241
+ print "qemu-system-aarch64: NOT INSTALLED"
52
242
  fi
243
+ if command -v qemu-aarch64-static >/dev/null 2>&1; then print "qemu-aarch64-static: $(command -v qemu-aarch64-static)"; else print "qemu-aarch64-static: NOT INSTALLED"; fi
244
+ print "libvirtd status:"
245
+ systemctl status libvirtd --no-pager || true
246
+
247
+ cat <<'EOF'
248
+
249
+ === Example Packer qemu builder snippets ===
250
+
251
+ # aarch64 on x86_64 host (use qemu-system-aarch64 with TCG):
252
+ {
253
+ "type": "qemu",
254
+ "qemu_binary": "/usr/local/bin/qemu-system-aarch64",
255
+ "accelerator": "tcg",
256
+ "format": "raw",
257
+ "disk_size": "8192",
258
+ "headless": true,
259
+ "qemuargs": [
260
+ ["-machine", "virt,highmem=on"],
261
+ ["-cpu", "cortex-a57"],
262
+ ["-bios", "/usr/share/edk2-aarch64/QEMU_EFI.fd"],
263
+ ["-device", "virtio-net-device,netdev=net0"],
264
+ ["-netdev", "user,id=net0,hostfwd=tcp::2222-:22"]
265
+ ]
266
+ }
267
+
268
+ # x86_64 on arm64 host (use kvm when available):
269
+ {
270
+ "type": "qemu",
271
+ "qemu_binary": "/usr/bin/qemu-system-x86_64",
272
+ "accelerator": "kvm",
273
+ "format": "raw",
274
+ "disk_size": "8192",
275
+ "headless": true,
276
+ "qemuargs": [
277
+ ["-machine", "pc,q35"],
278
+ ["-cpu", "host"],
279
+ ["-bios", "/usr/share/ovmf/OVMF_CODE.fd"],
280
+ ["-device", "virtio-net-pci,netdev=net0"],
281
+ ["-netdev", "user,id=net0,hostfwd=tcp::2223-:22"]
282
+ ]
283
+ }
284
+
285
+ EOF
286
+
287
+ print "Done. If any package failed to install due to repo availability, consider enabling CRB/powertools, adding a trusted COPR or rebuild repo for qemu, or compiling QEMU locally as the script offered."
288
+
289
+ exit 0
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ BASHRC="$HOME/.bashrc"
5
+
6
+ # Check whether a "ports" function is already defined
7
+ if grep -Eq '^\s*(function\s+ports|ports\s*\(\))' "$BASHRC"; then
8
+ echo "The 'ports' function already exists in $BASHRC. Nothing was changed."
9
+ exit 0
10
+ fi
11
+
12
+ # Append the function to the end of ~/.bashrc
13
+ cat >> "$BASHRC" <<'EOF'
14
+
15
+ # >>> ports function added by script >>>
16
+ ports() {
17
+ # no arguments: show listening TCP+UDP sockets
18
+ if [ -z "$1" ]; then
19
+ sudo ss -ltnup
20
+ return
21
+ fi
22
+
23
+ # with an argument: print the header and lines that exactly match the given port
24
+ local p="$1"
25
+ sudo ss -tunap | awk -v p="$p" 'NR==1 || $0 ~ (":"p"($| )")'
26
+ }
27
+ # <<< ports function added by script <<<
28
+ EOF
29
+
30
+ echo "Function 'ports' was added to $BASHRC."
31
+ echo "Load it now with: source ~/.bashrc (or open a new terminal)."
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ url="${1:-}"
5
+ [[ "$url" =~ ^tftp://([^/]+)(/.+)$ ]] || { echo "Usage: $0 tftp://host/path"; exit 2; }
6
+ host="${BASH_REMATCH[1]}"
7
+ path="${BASH_REMATCH[2]}"
8
+ outfile="/tmp/$(basename "$path")"
9
+
10
+ if command -v curl >/dev/null 2>&1; then
11
+ curl -f --silent --output "$outfile" "$url" && echo "OK: $outfile ($(stat -c%s "$outfile") bytes)" || { echo "curl: failed"; exit 3; }
12
+ elif command -v tftp >/dev/null 2>&1; then
13
+ printf "get %s %s\nquit\n" "$path" "$outfile" | tftp "$host" \
14
+ && [[ -s "$outfile" ]] \
15
+ && echo "OK: $outfile ($(stat -c%s "$outfile") bytes)" \
16
+ || { echo "tftp: failed"; exit 3; }
17
+ else
18
+ echo "Install 'curl' or 'tftp-client' (sudo dnf install -y curl tftp-client)"; exit 4
19
+ fi
@@ -61,6 +61,21 @@ const DocumentController = {
61
61
  });
62
62
  }
63
63
  },
64
+ patch: async (req, res, options) => {
65
+ try {
66
+ const result = await DocumentService.patch(req, res, options);
67
+ return res.status(200).json({
68
+ status: 'success',
69
+ data: result,
70
+ });
71
+ } catch (error) {
72
+ logger.error(error, error.stack);
73
+ return res.status(400).json({
74
+ status: 'error',
75
+ message: error.message,
76
+ });
77
+ }
78
+ },
64
79
  };
65
80
 
66
81
  export { DocumentController };
@@ -19,6 +19,16 @@ const DocumentSchema = new Schema(
19
19
  type: Schema.Types.ObjectId,
20
20
  ref: 'File',
21
21
  },
22
+ share: {
23
+ copyShareLinkEvent: [
24
+ {
25
+ year: { type: Number },
26
+ month: { type: Number },
27
+ day: { type: Number },
28
+ count: { type: Number, default: 0 },
29
+ },
30
+ ],
31
+ },
22
32
  },
23
33
  {
24
34
  timestamps: true,
@@ -53,6 +63,10 @@ const DocumentDto = {
53
63
  };
54
64
  },
55
65
  },
66
+ getTotalCopyShareLinkCount: (document) => {
67
+ if (!document.share || !document.share.copyShareLinkEvent) return 0;
68
+ return document.share.copyShareLinkEvent.reduce((total, event) => total + (event.count || 0), 0);
69
+ },
56
70
  };
57
71
 
58
72
  export { DocumentSchema, DocumentModel, ProviderSchema, DocumentDto };
@@ -14,6 +14,7 @@ const DocumentRouter = (options) => {
14
14
  router.get(`/`, authMiddleware, async (req, res) => await DocumentController.get(req, res, options));
15
15
  router.put(`/:id`, authMiddleware, async (req, res) => await DocumentController.put(req, res, options));
16
16
  router.put(`/`, authMiddleware, async (req, res) => await DocumentController.put(req, res, options));
17
+ router.patch(`/:id/copy-share-link`, async (req, res) => await DocumentController.patch(req, res, options));
17
18
  router.delete(`/:id`, authMiddleware, async (req, res) => await DocumentController.delete(req, res, options));
18
19
  router.delete(`/`, authMiddleware, async (req, res) => await DocumentController.delete(req, res, options));
19
20
  return router;
@@ -64,20 +64,39 @@ const DocumentService = {
64
64
  const lastDoc = await Document.findOne(queryPayload, '_id').sort({ createdAt: 1 });
65
65
  const lastId = lastDoc ? lastDoc._id : null;
66
66
 
67
+ // Add totalCopyShareLinkCount to each document
68
+ const dataWithCounts = data.map((doc) => {
69
+ const docObj = doc.toObject ? doc.toObject() : doc;
70
+ return {
71
+ ...docObj,
72
+ totalCopyShareLinkCount: DocumentDto.getTotalCopyShareLinkCount(doc),
73
+ };
74
+ });
75
+
67
76
  return {
68
- data,
77
+ data: dataWithCounts,
69
78
  lastId,
70
79
  };
71
80
  }
72
81
 
73
82
  switch (req.params.id) {
74
- default:
75
- return await Document.find({
83
+ default: {
84
+ const data = await Document.find({
76
85
  userId: req.auth.user._id,
77
86
  ...(req.params.id ? { _id: req.params.id } : undefined),
78
87
  })
79
88
  .populate(DocumentDto.populate.file())
80
89
  .populate(DocumentDto.populate.mdFile());
90
+
91
+ // Add totalCopyShareLinkCount to each document
92
+ return data.map((doc) => {
93
+ const docObj = doc.toObject ? doc.toObject() : doc;
94
+ return {
95
+ ...docObj,
96
+ totalCopyShareLinkCount: DocumentDto.getTotalCopyShareLinkCount(doc),
97
+ };
98
+ });
99
+ }
81
100
  }
82
101
  },
83
102
  delete: async (req, res, options) => {
@@ -131,6 +150,45 @@ const DocumentService = {
131
150
  }
132
151
  }
133
152
  },
153
+ patch: async (req, res, options) => {
154
+ /** @type {import('./document.model.js').DocumentModel} */
155
+ const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
156
+
157
+ if (req.path.includes('/copy-share-link')) {
158
+ const document = await Document.findById(req.params.id);
159
+ if (!document) throw new Error('Document not found');
160
+
161
+ const now = new Date();
162
+ const year = now.getFullYear();
163
+ const month = now.getMonth() + 1; // 0-indexed
164
+ const day = now.getDate();
165
+
166
+ // Find existing entry for this year/month/day
167
+ const existingEventIndex = document.share?.copyShareLinkEvent?.findIndex(
168
+ (event) => event.year === year && event.month === month && event.day === day,
169
+ );
170
+
171
+ if (existingEventIndex !== undefined && existingEventIndex >= 0) {
172
+ // Increment existing count
173
+ document.share.copyShareLinkEvent[existingEventIndex].count += 1;
174
+ } else {
175
+ // Create new entry
176
+ if (!document.share) document.share = {};
177
+ if (!document.share.copyShareLinkEvent) document.share.copyShareLinkEvent = [];
178
+ document.share.copyShareLinkEvent.push({
179
+ year,
180
+ month,
181
+ day,
182
+ count: 1,
183
+ });
184
+ }
185
+
186
+ await document.save();
187
+ return document;
188
+ }
189
+
190
+ throw new Error('Invalid patch endpoint');
191
+ },
134
192
  };
135
193
 
136
194
  export { DocumentService };