tangled
alpha
login
or
join now
thecoded.prof
/
CMU
0
fork
atom
CMU Coding Bootcamp
0
fork
atom
overview
issues
pulls
pipelines
feat: backend unit / SQL & NoSQL
thecoded.prof
2 months ago
8cdf1444
be13960b
verified
This commit was signed with the committer's
known signature
.
thecoded.prof
SSH Key Fingerprint:
SHA256:ePn0u8NlJyz3J4Zl9MHOYW3f4XKoi5K1I4j53bwpG0U=
+747
-93
10 changed files
expand all
collapse all
unified
split
.gitignore
backend
.devenv.flake.nix
dec15.sql
devenv.lock
devenv.nix
schemas
supply-product.sql
nilla.nix
server
books.ts
bun.lock
package.json
+1
.gitignore
···
1
node_modules
0
···
1
node_modules
2
+
.devenv
+315
backend/.devenv.flake.nix
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
{
2
+
inputs =
3
+
let
4
+
version = "1.9.0";
5
+
system = "x86_64-linux";
6
+
devenv_root = "/home/coded/Programming/CMU/backend";
7
+
devenv_dotfile = "/home/coded/Programming/CMU/backend/.devenv";
8
+
devenv_dotfile_path = ./.devenv;
9
+
devenv_tmpdir = "/run/user/1000";
10
+
devenv_runtime = "/run/user/1000/devenv-684c98d";
11
+
devenv_istesting = false;
12
+
devenv_direnvrc_latest_version = 1;
13
+
container_name = null;
14
+
active_profiles = [ ];
15
+
hostname = "shorthair";
16
+
username = "coded";
17
+
18
+
in {
19
+
git-hooks.url = "github:cachix/git-hooks.nix";
20
+
git-hooks.inputs.nixpkgs.follows = "nixpkgs";
21
+
pre-commit-hooks.follows = "git-hooks";
22
+
nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
23
+
devenv.url = "github:cachix/devenv?dir=src/modules";
24
+
} // (if builtins.pathExists (devenv_dotfile_path + "/flake.json")
25
+
then builtins.fromJSON (builtins.readFile (devenv_dotfile_path + "/flake.json"))
26
+
else { });
27
+
28
+
outputs = { nixpkgs, ... }@inputs:
29
+
let
30
+
version = "1.9.0";
31
+
system = "x86_64-linux";
32
+
devenv_root = "/home/coded/Programming/CMU/backend";
33
+
devenv_dotfile = "/home/coded/Programming/CMU/backend/.devenv";
34
+
devenv_dotfile_path = ./.devenv;
35
+
devenv_tmpdir = "/run/user/1000";
36
+
devenv_runtime = "/run/user/1000/devenv-684c98d";
37
+
devenv_istesting = false;
38
+
devenv_direnvrc_latest_version = 1;
39
+
container_name = null;
40
+
active_profiles = [ ];
41
+
hostname = "shorthair";
42
+
username = "coded";
43
+
44
+
devenv =
45
+
if builtins.pathExists (devenv_dotfile_path + "/devenv.json")
46
+
then builtins.fromJSON (builtins.readFile (devenv_dotfile_path + "/devenv.json"))
47
+
else { };
48
+
49
+
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
50
+
51
+
# Function to create devenv configuration for a specific system with profiles support
52
+
mkDevenvForSystem = targetSystem:
53
+
let
54
+
getOverlays = inputName: inputAttrs:
55
+
map
56
+
(overlay:
57
+
let
58
+
input = inputs.${inputName} or (throw "No such input `${inputName}` while trying to configure overlays.");
59
+
in
60
+
input.overlays.${overlay} or (throw "Input `${inputName}` has no overlay called `${overlay}`. Supported overlays: ${nixpkgs.lib.concatStringsSep ", " (builtins.attrNames input.overlays)}"))
61
+
inputAttrs.overlays or [ ];
62
+
overlays = nixpkgs.lib.flatten (nixpkgs.lib.mapAttrsToList getOverlays (devenv.inputs or { }));
63
+
permittedUnfreePackages = devenv.nixpkgs.per-platform."${targetSystem}".permittedUnfreePackages or devenv.nixpkgs.permittedUnfreePackages or [ ];
64
+
pkgs = import nixpkgs {
65
+
system = targetSystem;
66
+
config = {
67
+
allowUnfree = devenv.nixpkgs.per-platform."${targetSystem}".allowUnfree or devenv.nixpkgs.allowUnfree or devenv.allowUnfree or false;
68
+
allowBroken = devenv.nixpkgs.per-platform."${targetSystem}".allowBroken or devenv.nixpkgs.allowBroken or devenv.allowBroken or false;
69
+
cudaSupport = devenv.nixpkgs.per-platform."${targetSystem}".cudaSupport or devenv.nixpkgs.cudaSupport or false;
70
+
cudaCapabilities = devenv.nixpkgs.per-platform."${targetSystem}".cudaCapabilities or devenv.nixpkgs.cudaCapabilities or [ ];
71
+
permittedInsecurePackages = devenv.nixpkgs.per-platform."${targetSystem}".permittedInsecurePackages or devenv.nixpkgs.permittedInsecurePackages or devenv.permittedInsecurePackages or [ ];
72
+
allowUnfreePredicate = if (permittedUnfreePackages != [ ]) then (pkg: builtins.elem (nixpkgs.lib.getName pkg) permittedUnfreePackages) else (_: false);
73
+
};
74
+
inherit overlays;
75
+
};
76
+
lib = pkgs.lib;
77
+
importModule = path:
78
+
if lib.hasPrefix "./" path
79
+
then if lib.hasSuffix ".nix" path
80
+
then ./. + (builtins.substring 1 255 path)
81
+
else ./. + (builtins.substring 1 255 path) + "/devenv.nix"
82
+
else if lib.hasPrefix "../" path
83
+
then throw "devenv: ../ is not supported for imports"
84
+
else
85
+
let
86
+
paths = lib.splitString "/" path;
87
+
name = builtins.head paths;
88
+
input = inputs.${name} or (throw "Unknown input ${name}");
89
+
subpath = "/${lib.concatStringsSep "/" (builtins.tail paths)}";
90
+
devenvpath = "${input}" + subpath;
91
+
devenvdefaultpath = devenvpath + "/devenv.nix";
92
+
in
93
+
if lib.hasSuffix ".nix" devenvpath
94
+
then devenvpath
95
+
else if builtins.pathExists devenvdefaultpath
96
+
then devenvdefaultpath
97
+
else throw (devenvdefaultpath + " file does not exist for input ${name}.");
98
+
99
+
# Phase 1: Base evaluation to extract profile definitions
100
+
baseProject = pkgs.lib.evalModules {
101
+
specialArgs = inputs // { inherit inputs; };
102
+
modules = [
103
+
({ config, ... }: {
104
+
_module.args.pkgs = pkgs.appendOverlays (config.overlays or [ ]);
105
+
})
106
+
(inputs.devenv.modules + /top-level.nix)
107
+
{
108
+
devenv.cliVersion = version;
109
+
devenv.root = devenv_root;
110
+
devenv.dotfile = devenv_dotfile;
111
+
}
112
+
({ options, ... }: {
113
+
config.devenv = lib.mkMerge [
114
+
(pkgs.lib.optionalAttrs (builtins.hasAttr "tmpdir" options.devenv) {
115
+
tmpdir = devenv_tmpdir;
116
+
})
117
+
(pkgs.lib.optionalAttrs (builtins.hasAttr "isTesting" options.devenv) {
118
+
isTesting = devenv_istesting;
119
+
})
120
+
(pkgs.lib.optionalAttrs (builtins.hasAttr "runtime" options.devenv) {
121
+
runtime = devenv_runtime;
122
+
})
123
+
(pkgs.lib.optionalAttrs (builtins.hasAttr "direnvrcLatestVersion" options.devenv) {
124
+
direnvrcLatestVersion = devenv_direnvrc_latest_version;
125
+
})
126
+
];
127
+
})
128
+
(pkgs.lib.optionalAttrs (container_name != null) {
129
+
container.isBuilding = pkgs.lib.mkForce true;
130
+
containers.${container_name}.isBuilding = true;
131
+
})
132
+
] ++ (map importModule (devenv.imports or [ ])) ++ [
133
+
(if builtins.pathExists ./devenv.nix then ./devenv.nix else { })
134
+
(devenv.devenv or { })
135
+
(if builtins.pathExists ./devenv.local.nix then ./devenv.local.nix else { })
136
+
(if builtins.pathExists (devenv_dotfile_path + "/cli-options.nix") then import (devenv_dotfile_path + "/cli-options.nix") else { })
137
+
];
138
+
};
139
+
140
+
# Phase 2: Extract and apply profiles using extendModules with priority overrides
141
+
project =
142
+
let
143
+
# Build ordered list of profile names: hostname -> user -> manual
144
+
manualProfiles = active_profiles;
145
+
currentHostname = hostname;
146
+
currentUsername = username;
147
+
hostnameProfiles = lib.optional (currentHostname != "" && builtins.hasAttr currentHostname (baseProject.config.profiles.hostname or { })) "hostname.${currentHostname}";
148
+
userProfiles = lib.optional (currentUsername != "" && builtins.hasAttr currentUsername (baseProject.config.profiles.user or { })) "user.${currentUsername}";
149
+
150
+
# Ordered list of profiles to activate
151
+
orderedProfiles = hostnameProfiles ++ userProfiles ++ manualProfiles;
152
+
153
+
# Resolve profile extends with cycle detection
154
+
resolveProfileExtends = profileName: visited:
155
+
if builtins.elem profileName visited then
156
+
throw "Circular dependency detected in profile extends: ${lib.concatStringsSep " -> " visited} -> ${profileName}"
157
+
else
158
+
let
159
+
profile = getProfileConfig profileName;
160
+
extends = profile.extends or [ ];
161
+
newVisited = visited ++ [ profileName ];
162
+
extendedProfiles = lib.flatten (map (name: resolveProfileExtends name newVisited) extends);
163
+
in
164
+
extendedProfiles ++ [ profileName ];
165
+
166
+
# Get profile configuration by name from baseProject
167
+
getProfileConfig = profileName:
168
+
if lib.hasPrefix "hostname." profileName then
169
+
let name = lib.removePrefix "hostname." profileName;
170
+
in baseProject.config.profiles.hostname.${name}
171
+
else if lib.hasPrefix "user." profileName then
172
+
let name = lib.removePrefix "user." profileName;
173
+
in baseProject.config.profiles.user.${name}
174
+
else
175
+
let
176
+
availableProfiles = builtins.attrNames (baseProject.config.profiles or { });
177
+
hostnameProfiles = map (n: "hostname.${n}") (builtins.attrNames (baseProject.config.profiles.hostname or { }));
178
+
userProfiles = map (n: "user.${n}") (builtins.attrNames (baseProject.config.profiles.user or { }));
179
+
allAvailableProfiles = availableProfiles ++ hostnameProfiles ++ userProfiles;
180
+
in
181
+
baseProject.config.profiles.${profileName} or (throw "Profile '${profileName}' not found. Available profiles: ${lib.concatStringsSep ", " allAvailableProfiles}");
182
+
183
+
# Fold over ordered profiles to build final list with extends
184
+
expandedProfiles = lib.foldl'
185
+
(acc: profileName:
186
+
let
187
+
allProfileNames = resolveProfileExtends profileName [ ];
188
+
in
189
+
acc ++ allProfileNames
190
+
) [ ]
191
+
orderedProfiles;
192
+
193
+
# Map over expanded profiles and apply priorities
194
+
allPrioritizedModules = lib.imap0
195
+
(index: profileName:
196
+
let
197
+
# Decrement priority for each profile (lower = higher precedence)
198
+
# Start with the next lowest priority after the default priority for values (100)
199
+
profilePriority = (lib.modules.defaultOverridePriority - 1) - index;
200
+
profileConfig = getProfileConfig profileName;
201
+
202
+
# Support overriding both plain attrset modules and functions
203
+
applyModuleOverride = config:
204
+
if builtins.isFunction config
205
+
then
206
+
let
207
+
wrapper = args: applyOverrideRecursive (config args);
208
+
in
209
+
lib.mirrorFunctionArgs config wrapper
210
+
else applyOverrideRecursive config;
211
+
212
+
# Apply overrides recursively
213
+
applyOverrideRecursive = config:
214
+
if lib.isAttrs config && config ? _type
215
+
then config # Don't override values with existing type metadata
216
+
else if lib.isAttrs config
217
+
then lib.mapAttrs (_: applyOverrideRecursive) config
218
+
else lib.mkOverride profilePriority config;
219
+
220
+
# Apply priority overrides recursively to the deferredModule imports structure
221
+
prioritizedConfig = (
222
+
profileConfig.module // {
223
+
imports = lib.map
224
+
(importItem:
225
+
importItem // {
226
+
imports = lib.map
227
+
(nestedImport:
228
+
applyModuleOverride nestedImport
229
+
)
230
+
(importItem.imports or [ ]);
231
+
}
232
+
)
233
+
(profileConfig.module.imports or [ ]);
234
+
}
235
+
);
236
+
in
237
+
prioritizedConfig
238
+
)
239
+
expandedProfiles;
240
+
in
241
+
if allPrioritizedModules == [ ]
242
+
then baseProject
243
+
else baseProject.extendModules { modules = allPrioritizedModules; };
244
+
245
+
config = project.config;
246
+
247
+
options = pkgs.nixosOptionsDoc {
248
+
options = builtins.removeAttrs project.options [ "_module" ];
249
+
warningsAreErrors = false;
250
+
# Unpack Nix types, e.g. literalExpression, mDoc.
251
+
transformOptions =
252
+
let isDocType = v: builtins.elem v [ "literalDocBook" "literalExpression" "literalMD" "mdDoc" ];
253
+
in lib.attrsets.mapAttrs (_: v:
254
+
if v ? _type && isDocType v._type then
255
+
v.text
256
+
else if v ? _type && v._type == "derivation" then
257
+
v.name
258
+
else
259
+
v
260
+
);
261
+
};
262
+
263
+
# Recursively search for outputs in the config.
264
+
# This is used when not building a specific output by attrpath.
265
+
build = options: config:
266
+
lib.concatMapAttrs
267
+
(name: option:
268
+
if lib.isOption option then
269
+
let typeName = option.type.name or "";
270
+
in
271
+
if builtins.elem typeName [ "output" "outputOf" ] then
272
+
{ ${name} = config.${name}; }
273
+
else { }
274
+
else if builtins.isAttrs option && !lib.isDerivation option then
275
+
let v = build option config.${name};
276
+
in if v != { } then {
277
+
${name} = v;
278
+
} else { }
279
+
else { }
280
+
)
281
+
options;
282
+
in
283
+
{
284
+
inherit config options build;
285
+
shell = config.shell;
286
+
packages = {
287
+
optionsJSON = options.optionsJSON;
288
+
# deprecated
289
+
inherit (config) info procfileScript procfileEnv procfile;
290
+
ci = config.ciDerivation;
291
+
};
292
+
};
293
+
294
+
# Generate per-system devenv configurations
295
+
perSystem = nixpkgs.lib.genAttrs systems mkDevenvForSystem;
296
+
297
+
# Default devenv for the current system
298
+
currentSystemDevenv = perSystem.${system};
299
+
in
300
+
{
301
+
devShell = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.shell);
302
+
packages = nixpkgs.lib.genAttrs systems (s: perSystem.${s}.packages);
303
+
304
+
# Per-system devenv configurations
305
+
devenv = {
306
+
# Default devenv for the current system
307
+
inherit (currentSystemDevenv) config options build shell packages;
308
+
# Per-system devenv configurations
309
+
inherit perSystem;
310
+
};
311
+
312
+
# Legacy build output
313
+
build = currentSystemDevenv.build currentSystemDevenv.options currentSystemDevenv.config;
314
+
};
315
+
}
+116
backend/dec15.sql
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
-- CREATE
2
+
INSERT INTO
3
+
SUPPLY_CHAIN.SUPPLIER
4
+
VALUES
5
+
(
6
+
'amaz',
7
+
'amazon',
8
+
'410 Terry Ave N',
9
+
'Seattle',
10
+
0,
11
+
'SILVER'
12
+
),
13
+
(
14
+
'goog',
15
+
'google',
16
+
'1600 Amphitheatre Parkway',
17
+
'Mountain View',
18
+
0,
19
+
'GOLD'
20
+
);
21
+
22
+
INSERT INTO
23
+
SUPPLY_CHAIN.PRODUCT
24
+
VALUES
25
+
('whwine', 'white wine', 'white', 100, ''),
26
+
('rewine', 'red wine', 'red', 150, ''),
27
+
('rowine', 'rose wine', 'rose', 0, ''),
28
+
('prosec', 'prosecco', 'sparkling', 500, ''),
29
+
('clubso', 'club soda', 'sparkling', 200, '');
30
+
31
+
INSERT INTO
32
+
SUPPLY_CHAIN.SUPPLIES
33
+
VALUES
34
+
('amaz', 'whwine', 10.22, '17:00'),
35
+
('goog', 'rowine', 5.16, '23:00'),
36
+
('amaz', 'rewine', 7.12, '19:00'),
37
+
('goog', 'clubso', 3.07, '03:00'),
38
+
('amaz', 'prosec', 9.99, '05:00');
39
+
40
+
INSERT INTO
41
+
SUPPLY_CHAIN.PURCHASE_ORDER
42
+
VALUES
43
+
('aaaaaaa', '2005-01-01', 'amaz'),
44
+
('bbbbbbb', '2017-05-24', 'goog'),
45
+
('ccccccc', '2013-09-30', 'amaz'),
46
+
('ddddddd', '2020-12-01', 'goog'),
47
+
('eeeeeee', '2025-12-16', 'amaz');
48
+
49
+
INSERT INTO
50
+
SUPPLY_CHAIN.PO_LINE
51
+
VALUES
52
+
('aaaaaaa', 'whwine', 5),
53
+
('bbbbbbb', 'rowine', 50),
54
+
('ccccccc', 'rewine', 10),
55
+
('ddddddd', 'clubso', 150),
56
+
('eeeeeee', 'prosec', 200);
57
+
58
+
-- READ
59
+
SELECT
60
+
*
61
+
FROM
62
+
SUPPLY_CHAIN.SUPPLIER;
63
+
64
+
SELECT
65
+
*
66
+
FROM
67
+
SUPPLY_CHAIN.PRODUCT;
68
+
69
+
SELECT
70
+
*
71
+
FROM
72
+
SUPPLY_CHAIN.SUPPLIES;
73
+
74
+
SELECT
75
+
*
76
+
FROM
77
+
SUPPLY_CHAIN.PURCHASE_ORDER;
78
+
79
+
SELECT
80
+
P.PRODNR,
81
+
P.PRODNAME,
82
+
P.PRODTYPE,
83
+
P.AVAILABLE_QUANTITY,
84
+
PL.PONR,
85
+
PL.QUANTITY
86
+
FROM
87
+
SUPPLY_CHAIN.PRODUCT P
88
+
RIGHT JOIN SUPPLY_CHAIN.PO_LINE PL ON P.PRODNR = PL.PRODNR
89
+
WHERE
90
+
P.AVAILABLE_QUANTITY > 10;
91
+
92
+
-- UPDATE
93
+
UPDATE SUPPLY_CHAIN.supplier
94
+
SET
95
+
SUPSTATUS = 100
96
+
WHERE
97
+
SUPNR = 'amaz';
98
+
99
+
UPDATE SUPPLY_CHAIN.PRODUCT
100
+
SET
101
+
"available_quantity" = 10
102
+
WHERE
103
+
AVAILABLE_QUANTITY < 10;
104
+
105
+
-- DELETE
106
+
DELETE FROM SUPPLY_CHAIN.PURCHASE_ORDER WHERE PONR = 'aaaaaaa';
107
+
108
+
DELETE FROM SUPPLY_CHAIN.SUPPLIER;
109
+
110
+
DELETE FROM SUPPLY_CHAIN.PRODUCT;
111
+
112
+
DELETE FROM SUPPLY_CHAIN.PO_LINE;
113
+
114
+
DELETE FROM SUPPLY_CHAIN.PURCHASE_ORDER;
115
+
116
+
DELETE FROM SUPPLY_CHAIN.SUPPLIES;
+103
backend/devenv.lock
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
{
2
+
"nodes": {
3
+
"devenv": {
4
+
"locked": {
5
+
"dir": "src/modules",
6
+
"lastModified": 1765898645,
7
+
"owner": "cachix",
8
+
"repo": "devenv",
9
+
"rev": "b1ca338a97209fa6cdd18dede3f82e284cbcfb0d",
10
+
"type": "github"
11
+
},
12
+
"original": {
13
+
"dir": "src/modules",
14
+
"owner": "cachix",
15
+
"repo": "devenv",
16
+
"type": "github"
17
+
}
18
+
},
19
+
"flake-compat": {
20
+
"flake": false,
21
+
"locked": {
22
+
"lastModified": 1765121682,
23
+
"owner": "edolstra",
24
+
"repo": "flake-compat",
25
+
"rev": "65f23138d8d09a92e30f1e5c87611b23ef451bf3",
26
+
"type": "github"
27
+
},
28
+
"original": {
29
+
"owner": "edolstra",
30
+
"repo": "flake-compat",
31
+
"type": "github"
32
+
}
33
+
},
34
+
"git-hooks": {
35
+
"inputs": {
36
+
"flake-compat": "flake-compat",
37
+
"gitignore": "gitignore",
38
+
"nixpkgs": [
39
+
"nixpkgs"
40
+
]
41
+
},
42
+
"locked": {
43
+
"lastModified": 1765464257,
44
+
"owner": "cachix",
45
+
"repo": "git-hooks.nix",
46
+
"rev": "09e45f2598e1a8499c3594fe11ec2943f34fe509",
47
+
"type": "github"
48
+
},
49
+
"original": {
50
+
"owner": "cachix",
51
+
"repo": "git-hooks.nix",
52
+
"type": "github"
53
+
}
54
+
},
55
+
"gitignore": {
56
+
"inputs": {
57
+
"nixpkgs": [
58
+
"git-hooks",
59
+
"nixpkgs"
60
+
]
61
+
},
62
+
"locked": {
63
+
"lastModified": 1762808025,
64
+
"owner": "hercules-ci",
65
+
"repo": "gitignore.nix",
66
+
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
67
+
"type": "github"
68
+
},
69
+
"original": {
70
+
"owner": "hercules-ci",
71
+
"repo": "gitignore.nix",
72
+
"type": "github"
73
+
}
74
+
},
75
+
"nixpkgs": {
76
+
"locked": {
77
+
"lastModified": 1764580874,
78
+
"owner": "cachix",
79
+
"repo": "devenv-nixpkgs",
80
+
"rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d",
81
+
"type": "github"
82
+
},
83
+
"original": {
84
+
"owner": "cachix",
85
+
"ref": "rolling",
86
+
"repo": "devenv-nixpkgs",
87
+
"type": "github"
88
+
}
89
+
},
90
+
"root": {
91
+
"inputs": {
92
+
"devenv": "devenv",
93
+
"git-hooks": "git-hooks",
94
+
"nixpkgs": "nixpkgs",
95
+
"pre-commit-hooks": [
96
+
"git-hooks"
97
+
]
98
+
}
99
+
}
100
+
},
101
+
"root": "root",
102
+
"version": 7
103
+
}
+18
backend/devenv.nix
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
{
2
+
services.postgres = {
3
+
enable = true;
4
+
initialDatabases = [
5
+
{
6
+
name = "supply-product";
7
+
schema = ./schemas/supply-product.sql;
8
+
}
9
+
];
10
+
listen_addresses = "127.0.0.1";
11
+
hbaConf = ''
12
+
local all all trust
13
+
host all all 127.0.0.1/32 trust
14
+
'';
15
+
};
16
+
17
+
services.mongodb.enable = true;
18
+
}
+64
backend/schemas/supply-product.sql
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
CREATE SCHEMA SUPPLY_CHAIN;
2
+
3
+
CREATE TABLE SUPPLY_CHAIN.SUPPLIER (
4
+
supnr CHAR(4) PRIMARY KEY,
5
+
supname VARCHAR(40) NOT NULL,
6
+
supaddress VARCHAR(50),
7
+
supcity VARCHAR(20),
8
+
supstatus SMALLINT CHECK (supstatus >= 0 AND supstatus <= 100),
9
+
supcategory VARCHAR(10) DEFAULT 'SILVER' NOT NULL
10
+
);
11
+
12
+
CREATE TYPE SUPPLY_CHAIN.product_type AS ENUM ('white', 'red', 'rose', 'sparkling');
13
+
14
+
CREATE TABLE SUPPLY_CHAIN.PRODUCT (
15
+
prodnr CHAR(6) PRIMARY KEY,
16
+
prodname VARCHAR(60) UNIQUE NOT NULL,
17
+
prodtype SUPPLY_CHAIN.product_type,
18
+
available_quantity INTEGER,
19
+
prodimage BYTEA
20
+
);
21
+
22
+
CREATE TABLE SUPPLY_CHAIN.SUPPLIES (
23
+
supnr CHAR(4)
24
+
NOT NULL
25
+
REFERENCES SUPPLY_CHAIN.SUPPLIER(supnr)
26
+
ON DELETE CASCADE
27
+
ON UPDATE CASCADE,
28
+
prodnr CHAR(6)
29
+
NOT NULL
30
+
REFERENCES SUPPLY_CHAIN.PRODUCT(prodnr)
31
+
ON DELETE CASCADE
32
+
ON UPDATE CASCADE,
33
+
purchase_price DECIMAL(8,2),
34
+
deliv_period TIME,
35
+
PRIMARY KEY (supnr, prodnr)
36
+
);
37
+
38
+
COMMENT ON COLUMN SUPPLY_CHAIN.SUPPLIES.purchase_price IS 'in EUR';
39
+
COMMENT ON COLUMN SUPPLY_CHAIN.SUPPLIES.deliv_period IS 'in days';
40
+
41
+
CREATE TABLE SUPPLY_CHAIN.PURCHASE_ORDER (
42
+
ponr CHAR(7) PRIMARY KEY,
43
+
podate DATE,
44
+
supnr CHAR(4)
45
+
NOT NULL
46
+
REFERENCES SUPPLY_CHAIN.SUPPLIER(supnr)
47
+
ON DELETE CASCADE
48
+
ON UPDATE CASCADE
49
+
);
50
+
51
+
CREATE TABLE SUPPLY_CHAIN.PO_LINE (
52
+
ponr CHAR(7)
53
+
NOT NULL
54
+
REFERENCES SUPPLY_CHAIN.PURCHASE_ORDER(ponr)
55
+
ON DELETE CASCADE
56
+
ON UPDATE CASCADE,
57
+
prodnr CHAR(6)
58
+
NOT NULL
59
+
REFERENCES SUPPLY_CHAIN.PRODUCT(prodnr)
60
+
ON DELETE CASCADE
61
+
ON UPDATE CASCADE,
62
+
quantity INTEGER,
63
+
PRIMARY KEY (ponr, prodnr)
64
+
);
+19
-2
nilla.nix
···
8
nilla = import pins.nilla;
9
in
10
nilla.create (
11
-
{ config }:
12
{
13
config = {
14
inputs = {
···
124
125
shell =
126
{
127
-
pkgs,
128
mkShell,
0
129
}:
130
mkShell {
131
shellHook = ''
···
139
pkgs.nixd
140
pkgs.nil
141
pkgs.nodePackages.live-server
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
142
];
143
};
144
};
···
8
nilla = import pins.nilla;
9
in
10
nilla.create (
11
+
{ config, lib }:
12
{
13
config = {
14
inputs = {
···
124
125
shell =
126
{
0
127
mkShell,
128
+
pkgs
129
}:
130
mkShell {
131
shellHook = ''
···
139
pkgs.nixd
140
pkgs.nil
141
pkgs.nodePackages.live-server
142
+
];
143
+
};
144
+
};
145
+
shells.backend = {
146
+
systems = ["x86_64-linux"];
147
+
shell = {mkShell, lib, system}:
148
+
let
149
+
pkgs = import pins.nixpkgs {
150
+
inherit system;
151
+
config.allowUnfree = true;
152
+
};
153
+
in mkShell {
154
+
packages = [
155
+
pkgs.jetbrains.datagrip
156
+
pkgs.devenv
157
+
pkgs.mongosh
158
+
pkgs.mongodb-compass
159
];
160
};
161
};
+84
-90
server/books.ts
···
3
type Request,
4
type Response,
5
} from "express";
6
-
import { writeFile, readFile, exists } from "fs/promises";
7
8
const ISBN13 =
9
/^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)[\d-]+$/;
10
11
interface Book {
12
-
id: string;
13
title: string;
14
-
author: string;
0
0
0
0
0
0
15
}
16
17
-
const initBooks: () => Promise<void> = async () => {
18
-
await writeFile(
19
-
"books.json",
20
-
JSON.stringify([
21
-
{
22
-
id: "9780553212471",
23
-
title: "Frankenstein",
24
-
author: "Mary Shelley",
25
-
},
26
-
{
27
-
id: "9780060935467",
28
-
title: "To Kill a Mockingbird",
29
-
author: "Harper Lee",
30
-
},
31
-
{
32
-
id: "9780141439518",
33
-
title: "Pride and Prejudice",
34
-
author: "Jane Austen",
35
-
},
36
-
]),
37
-
);
38
-
};
39
-
40
enum ErrorType {
41
NotFound,
42
InvalidId,
43
BadData,
44
AlreadyExists,
45
}
0
0
0
0
46
47
class BookError extends Error {
48
public readonly status: number;
···
74
}
75
76
const getBooks: () => Promise<Book[]> = async () => {
77
-
if (!(await exists("books.json"))) {
78
-
await initBooks();
79
-
}
80
-
const file = await readFile("books.json", "utf-8");
81
-
if (file.length < 4) {
82
-
await initBooks();
83
-
return await getBooks();
84
-
}
85
-
return JSON.parse(file);
86
};
87
88
-
const updateBook = async (task: Book): Promise<void> => {
89
-
const books = await getBooks();
90
-
const index = books.findIndex((b) => b.id === task.id);
91
-
if (index !== -1) {
92
-
books[index] = task;
93
-
} else {
94
-
books.push(task);
95
-
}
96
-
await writeFile("books.json", JSON.stringify(books));
97
};
98
99
-
const removeBook = async (id: string): Promise<void> => {
100
-
const books = await getBooks();
101
-
const index = books.findIndex((b) => b.id === id);
102
-
if (index !== -1) {
103
-
books.splice(index, 1);
104
-
await writeFile("books.json", JSON.stringify(books));
105
}
106
};
107
···
123
}
124
125
const keyTypes = {
126
-
id: "ISBN13 code",
127
title: "string",
128
-
author: "string",
0
0
0
0
0
0
129
};
130
131
-
const validateBook = (book: { [key: string]: any }): Book => {
132
-
let missingKeys = ["id", "title", "author"].filter(
133
(key) => !Object.keys(book).includes(key),
134
);
135
let extraKeys = Object.keys(book).filter(
136
-
(key) => !["id", "title", "author"].includes(key),
137
);
138
let badValues = Object.entries(book)
139
.filter(([key, value]) => {
140
-
if (key === "id") return typeof value !== "string" || !ISBN13.test(value);
0
141
if (key === "title") return typeof value !== "string";
142
-
if (key === "author") return typeof value !== "string";
0
0
0
0
143
return false;
144
})
145
.map(
146
([key, _value]) =>
147
[key, keyTypes[key as keyof typeof keyTypes]] as [string, string],
148
);
149
-
if (missingKeys.length > 0 || extraKeys.length > 0 || badValues.length > 0) {
0
0
0
0
150
throw new BadDataIssues(missingKeys, extraKeys, badValues);
151
}
152
return book as Book;
···
169
next();
170
};
171
0
0
0
0
0
0
0
172
const errorHandler = (
173
err: Error,
174
_req: Request,
···
176
_next: NextFunction,
177
) => {
178
if (err instanceof BookError) {
179
-
let msg = err.message.replace("{{id}}", res.locals.id ?? "");
180
181
let obj: Map<string, any> = new Map<string, any>([
182
["error", `${err.name}: ${msg}`],
···
201
}
202
};
203
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
204
const router = express.Router();
205
206
router.use(express.json());
207
-
router.use((req, res, next) => {
208
console.log(`Recieved a ${req.method} request to ${req.url}`);
209
next();
210
});
···
217
router.post("/", async (req, res) => {
218
const books = await getBooks();
219
try {
220
-
const bookData = validateBook(req.body);
221
-
res.locals.id = bookData.id;
222
-
if (books.filter((b) => b.id === bookData.id).length > 0) {
223
throw new BookError(ErrorType.AlreadyExists);
224
}
225
-
await updateBook(bookData);
226
res.status(201).json(bookData);
227
} catch (err) {
228
if (err instanceof BookError) {
···
236
}
237
});
238
239
-
router.get("/:id", async (req, res) => {
240
-
res.locals.id = req.params.id;
241
-
if (!ISBN13.test(req.params.id)) {
242
-
throw new BookError(ErrorType.InvalidId);
243
-
}
244
const books = await getBooks();
245
-
const book = books.find((b) => b.id == req.params.id);
246
if (!book) throw new BookError(ErrorType.NotFound);
247
res.json(book);
248
});
249
250
-
router.put("/:id", async (req, res) => {
251
-
res.locals.id = req.params.id;
252
-
if (!ISBN13.test(req.params.id)) {
253
-
throw new BookError(ErrorType.InvalidId);
254
-
}
255
const books = await getBooks();
256
-
const book = books.find((b) => b.id == req.params.id);
257
if (!book) throw new BookError(ErrorType.NotFound);
258
-
const bookData = validateBook(req.body);
259
-
await updateBook(bookData);
260
res.sendStatus(204);
261
});
262
263
-
router.delete("/reset", async (_req, res) => {
264
-
await initBooks();
265
-
res.sendStatus(204);
266
-
});
267
-
268
-
router.delete("/:id", async (req, res) => {
269
-
res.locals.id = req.params.id;
270
-
if (!ISBN13.test(req.params.id)) {
271
-
throw new BookError(ErrorType.InvalidId);
272
-
}
273
const books = await getBooks();
274
-
const book = books.find((b) => b.id == req.params.id);
275
if (!book) throw new BookError(ErrorType.NotFound);
276
-
await removeBook(book.id);
277
res.sendStatus(204);
278
});
279
···
3
type Request,
4
type Response,
5
} from "express";
6
+
import { MongoClient } from "mongodb";
7
8
const ISBN13 =
9
/^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)[\d-]+$/;
10
11
interface Book {
0
12
title: string;
13
+
authors: string[];
14
+
isbn: string;
15
+
publicationYear: number;
16
+
genres: string[];
17
+
pageCount: number;
18
+
averageRating: number;
19
+
numberOfRatings: number;
20
}
21
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
22
enum ErrorType {
23
NotFound,
24
InvalidId,
25
BadData,
26
AlreadyExists,
27
}
28
+
29
+
const database = new MongoClient("mongodb://localhost:27017")
30
+
.db("library")
31
+
.collection<Book>("books");
32
33
class BookError extends Error {
34
public readonly status: number;
···
60
}
61
62
const getBooks: () => Promise<Book[]> = async () => {
63
+
return (await database.find().toArray()).map((b) => b as Book);
0
0
0
0
0
0
0
0
64
};
65
66
+
const updateBook = async (
67
+
isbn: string,
68
+
book: Partial<Omit<Book, "isbn">>,
69
+
): Promise<void> => {
70
+
database.updateOne({ isbn }, { $set: book });
0
0
0
0
71
};
72
73
+
const removeBook = async (isbn: string): Promise<void> => {
74
+
if ((await database.deleteOne({ isbn })).deletedCount === 0) {
75
+
throw new BookError(ErrorType.NotFound);
0
0
0
76
}
77
};
78
···
94
}
95
96
const keyTypes = {
0
97
title: "string",
98
+
author: "string array",
99
+
isbn: "ISBN13 code",
100
+
publicationYear: "integer",
101
+
genres: "string array",
102
+
pageCount: "integer",
103
+
averageRating: "float 0-10",
104
+
numberOfRatings: "integer",
105
};
106
107
+
const validateBook = (book: { [key: string]: any }, create: boolean): Book => {
108
+
let missingKeys = ["isbn", "title", "authors"].filter(
109
(key) => !Object.keys(book).includes(key),
110
);
111
let extraKeys = Object.keys(book).filter(
112
+
(key) => !["isbn", "title", "authors"].includes(key),
113
);
114
let badValues = Object.entries(book)
115
.filter(([key, value]) => {
116
+
if (key === "isbn")
117
+
return typeof value !== "string" || !ISBN13.test(value);
118
if (key === "title") return typeof value !== "string";
119
+
if (key === "authors")
120
+
return !(
121
+
Array.isArray(value) &&
122
+
value.every((author) => typeof author === "string")
123
+
);
124
return false;
125
})
126
.map(
127
([key, _value]) =>
128
[key, keyTypes[key as keyof typeof keyTypes]] as [string, string],
129
);
130
+
if (
131
+
(create && missingKeys.length > 0) ||
132
+
extraKeys.length > 0 ||
133
+
badValues.length > 0
134
+
) {
135
throw new BadDataIssues(missingKeys, extraKeys, badValues);
136
}
137
return book as Book;
···
154
next();
155
};
156
157
+
const validateISBN = (isbn: string): string => {
158
+
if (!ISBN13.test(isbn)) {
159
+
throw new BookError(ErrorType.InvalidId);
160
+
}
161
+
return isbn.split("").map(Number).join("");
162
+
};
163
+
164
const errorHandler = (
165
err: Error,
166
_req: Request,
···
168
_next: NextFunction,
169
) => {
170
if (err instanceof BookError) {
171
+
let msg = err.message.replace("{{id}}", res.locals.isbn ?? "");
172
173
let obj: Map<string, any> = new Map<string, any>([
174
["error", `${err.name}: ${msg}`],
···
193
}
194
};
195
196
+
const getISBN = async (req: Request, res: Response, next: NextFunction) => {
197
+
const isbn = req.params.isbn;
198
+
if (!isbn) {
199
+
throw new BookError(ErrorType.InvalidId);
200
+
}
201
+
try {
202
+
const validated = validateISBN(isbn);
203
+
res.locals.isbn = validated;
204
+
next();
205
+
} catch (err) {
206
+
if (err instanceof BookError) {
207
+
throw err;
208
+
} else {
209
+
res.status(500).json({ error: "Internal Server Error" });
210
+
return;
211
+
}
212
+
}
213
+
};
214
+
215
const router = express.Router();
216
217
router.use(express.json());
218
+
router.use((req, _res, next) => {
219
console.log(`Recieved a ${req.method} request to ${req.url}`);
220
next();
221
});
···
228
router.post("/", async (req, res) => {
229
const books = await getBooks();
230
try {
231
+
const bookData = validateBook(req.body, true);
232
+
res.locals.id = bookData.isbn;
233
+
if (books.filter((b) => b.isbn === bookData.isbn).length > 0) {
234
throw new BookError(ErrorType.AlreadyExists);
235
}
236
+
await updateBook(res.locals.id, bookData);
237
res.status(201).json(bookData);
238
} catch (err) {
239
if (err instanceof BookError) {
···
247
}
248
});
249
250
+
router.get("/:isbn", getISBN, async (_req, res) => {
0
0
0
0
251
const books = await getBooks();
252
+
const book = books.find((b) => b.isbn == res.locals.isbn);
253
if (!book) throw new BookError(ErrorType.NotFound);
254
res.json(book);
255
});
256
257
+
router.patch("/:isbn", getISBN, async (req, res) => {
0
0
0
0
258
const books = await getBooks();
259
+
const book = books.find((b) => b.isbn == res.locals.isbn);
260
if (!book) throw new BookError(ErrorType.NotFound);
261
+
const bookData = validateBook(req.body, false);
262
+
await updateBook(res.locals.isbn, bookData);
263
res.sendStatus(204);
264
});
265
266
+
router.delete("/:isbn", getISBN, async (_req, res) => {
0
0
0
0
0
0
0
0
0
267
const books = await getBooks();
268
+
const book = books.find((b) => b.isbn == res.locals.isbn);
269
if (!book) throw new BookError(ErrorType.NotFound);
270
+
await removeBook(book.isbn);
271
res.sendStatus(204);
272
});
273
+25
server/bun.lock
···
6
"dependencies": {
7
"@types/express": "^5.0.6",
8
"express": "^5.2.1",
0
9
},
10
"devDependencies": {
11
"@types/bun": "latest",
···
16
},
17
},
18
"packages": {
0
0
19
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
20
21
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
···
38
39
"@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
40
0
0
0
0
41
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
42
43
"body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
0
0
44
45
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
46
···
112
113
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
114
0
0
115
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
116
117
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
118
119
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
120
0
0
0
0
121
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
122
123
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
···
133
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
134
135
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
0
0
136
137
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
138
···
157
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
158
159
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
0
0
160
161
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
162
163
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
164
0
0
165
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
166
167
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
···
171
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
172
173
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
0
0
0
0
174
175
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
176
}
···
6
"dependencies": {
7
"@types/express": "^5.0.6",
8
"express": "^5.2.1",
9
+
"mongodb": "^7.0.0",
10
},
11
"devDependencies": {
12
"@types/bun": "latest",
···
17
},
18
},
19
"packages": {
20
+
"@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.4", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g=="],
21
+
22
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
23
24
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
···
41
42
"@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
43
44
+
"@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="],
45
+
46
+
"@types/whatwg-url": ["@types/whatwg-url@13.0.0", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q=="],
47
+
48
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
49
50
"body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
51
+
52
+
"bson": ["bson@7.0.0", "", {}, "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw=="],
53
54
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
55
···
121
122
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
123
124
+
"memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
125
+
126
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
127
128
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
129
130
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
131
132
+
"mongodb": ["mongodb@7.0.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.3.0", "bson": "^7.0.0", "mongodb-connection-string-url": "^7.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.806.0", "@mongodb-js/zstd": "^7.0.0", "gcp-metadata": "^7.0.1", "kerberos": "^7.0.0", "mongodb-client-encryption": ">=7.0.0 <7.1.0", "snappy": "^7.3.2", "socks": "^2.8.6" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg=="],
133
+
134
+
"mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.0", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og=="],
135
+
136
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
137
138
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
···
148
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
149
150
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
151
+
152
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
153
154
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
155
···
174
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
175
176
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
177
+
178
+
"sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="],
179
180
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
181
182
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
183
184
+
"tr46": ["tr46@5.1.1", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw=="],
185
+
186
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
187
188
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
···
192
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
193
194
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
195
+
196
+
"webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
197
+
198
+
"whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="],
199
200
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
201
}
+2
-1
server/package.json
···
11
},
12
"dependencies": {
13
"@types/express": "^5.0.6",
14
-
"express": "^5.2.1"
0
15
}
16
}
···
11
},
12
"dependencies": {
13
"@types/express": "^5.0.6",
14
+
"express": "^5.2.1",
15
+
"mongodb": "^7.0.0"
16
}
17
}