diff --git a/docs/01-notes-node-js-fundamental.md b/docs/01-notes-node-js-fundamental.md new file mode 100644 index 00000000..e78ee731 --- /dev/null +++ b/docs/01-notes-node-js-fundamental.md @@ -0,0 +1,83 @@ +# ๐Ÿ“” Node.js Study Notes - Fundamentals (2026 Edition) + +## ๐Ÿ—๏ธ 1. Foundations (Path & Process) + +* **`process.cwd()`**: Returns the "Current Working Directory". It's the robot saying: "I am standing exactly here". +* **`process.argv`**: The array that stores everything you type in the terminal after the command. +* **`path.resolve`**: Convert relative routes into absolute routes. +* **`path.join`**: Avoid errors by joining parts of routes in a secure way (handles `/` vs `\`). +* **`path.relative`**: Cleans the route and gives only the path relative to another (e.g., from workspace to file). +* **`path.extname`**: Returns the file extension (e.g., `.txt`). +* **`path.dirname`**: Returns the name of the parent directory. Useful to know where a file is sitting. + +--- + +## ๐Ÿ“‚ 2. File System (fs/promises) + +* **`fs/promises`**: Using the asynchronous version of the FS module so we can use `await`. +* **`readdir`**: Lists everything inside a folder. +* **`stat`**: Checks metadata (if it's a file, directory, and its size). +* **`readFile` / `writeFile`**: Basic commands to read content and create/overwrite files. +* **`mkdir`**: Creates a new directory. Used with `{ recursive: true }` to create nested folders (a/b/c) all at once. +* **`Buffer`**: A way to handle raw binary data. We used `Buffer.from(content, "base64")` to turn encoded text back into real files. +* **`.toString("base64")`**: Converts file content into a base64 string for easy storage in JSON. + +--- + +## โŒจ๏ธ 3. CLI & Terminal UI + +* **`readline`**: A module used to read input from the terminal line by line. Itโ€™s the "listening ear" of the app. +* **`process.uptime()`**: Returns how many seconds the Node process has been running. +* **`.toISOString()`**: Returns a string in standard ISO format (YYYY-MM-DDTHH:mm:ss.sssZ). +* **`\r` (Carriage Return)**: Moves the cursor back to the start of the line without jumping to the next one. Essential for "in-place" updates like progress bars. +* **ANSI Escape Codes**: Special sequences (like `\x1b[38;2;...`) used to color and format terminal text. +* **Hex to RGB**: Converting `#RRGGBB` into three numbers (Red, Green, Blue) so the terminal can apply colors. + +--- + +## ๐Ÿงฉ 4. Dynamic Modules + +* **`import()`**: A function-like expression that allows you to load a module asynchronously on the fly. +* **`pathToFileURL`**: Converts a system path into a URL (`file:///...`), necessary for dynamic imports on modern Node.js versions. + +--- + +## ๐Ÿ›ก๏ธ 5. Hashing & Security + +* **`createHash('sha256')`**: Creates a unique "digital fingerprint" (64 characters). If one bit changes, the hash changes completely. +* **`createReadStream`**: Opens a "river" of data. Instead of drinking the whole thing at once (avoiding memory crashes), we process it by "trickles" (chunks). +* **`pipeline`**: The "glue." It connects streams safely and handles errors automatically. It's the modern way to pipe data. + +--- + +## ๐ŸŒŠ 6. Streams & Transformations + +* **Transform Stream**: A duplex stream that modifies or transforms the data as it passes through (e.g., adding line numbers or filtering). +* **`process.stdin` / `process.stdout`**: The standard input (keyboard) and output (screen) streams of the process. +* **`chunk`**: A small piece of data (usually a Buffer) being processed in the stream pipeline. +* **Backpressure**: A situation where data is produced faster than it can be consumed; Streams handle this automatically to save memory. + +--- + +## ๐Ÿค 7. Compression (Zlib & Brotli) + +* **`zlib`**: The built-in module for compression and decompression. +* **Brotli (`.br`)**: A modern, high-efficiency compression algorithm from Google, superior to Gzip for text assets. +* **`createBrotliCompress` / `createBrotliDecompress`**: Transform streams used to shrink or restore data. + +--- + +## ๐Ÿงต 8. Worker Threads (Parallelism) + +* **Main Thread**: The primary execution path. It handles the event loop and delegates heavy CPU tasks. +* **Worker Thread**: An independent thread that runs alongside the main thread. +* **`parentPort`**: The communication channel (walkie-talkie) between the worker and the main thread. +* **Divide and Conquer**: Splitting a large problem into smaller chunks to be solved in parallel by multiple workers. + +--- + +## ๐Ÿ‘ถ 9. Child Processes + +* **`spawn`**: Launches a new process (like `ls`, `date`, or any terminal command). +* **`stdio: inherit / pipe`**: Connecting the child's input/output channels to the parent process. +* **Exit Code**: A number returned by the process when it finishes. `0` means success; anything else indicates an error. \ No newline at end of file diff --git a/docs/02-notes-data-processing-cli.md b/docs/02-notes-data-processing-cli.md new file mode 100644 index 00000000..4ac3a444 --- /dev/null +++ b/docs/02-notes-data-processing-cli.md @@ -0,0 +1,38 @@ +# ๐Ÿ› ๏ธ Data Processing CLI - Technical Implementation Notes + +## ๐ŸŽฎ 1. REPL & State Management +* **Persistent State**: The application maintains a `state` object (e.g., `{ currentDir: homedir() }`) that persists throughout the session. This allows commands like `cd` to update the global context of the app. +* **Readline Interface**: Used to create a continuous loop. It "listens" for user input, processes it through a dispatcher, and then prompts the user again. +* **Graceful Exit**: Implementing `.exit` and handling `SIGINT` (Ctrl+C) to ensure the terminal returns to its normal state properly. + +--- + +## โš™๏ธ 2. Professional Argument Parsing +* **Flag Extraction**: We built a custom parser to transform a raw string array `["--input", "data.csv"]` into a clean object `{ input: "data.csv" }`. +* **Boolean Flags**: The parser identifies flags without values (like `--save`) and assigns them a `true` value automatically. + +--- + +## ๐Ÿ›ค๏ธ 3. Relative & Absolute Path Resolution +* **Context-Aware Paths**: Every command uses a `resolvePath` utility. It combines the `currentDir` from the app state with the user's input string. +* **`path.resolve`**: Crucial for handling both relative paths (`./file.txt`) and absolute paths (`/Users/dian/file.txt`) seamlessly. + +--- + +## ๐Ÿ”’ 4. Authenticated Encryption (AES-256-GCM) +* **GCM Mode**: Unlike basic encryption, GCM provides "authenticated encryption," ensuring the data wasn't tampered with. +* **The "Secret Sandwich"**: We learned to structure the output file as: `Salt (16b)` + `IV (12b)` + `Encrypted Data` + `AuthTag (16b)`. +* **Scrypt**: Used for Key Derivation. It turns a simple password into a cryptographically strong 32-byte key using a random Salt. + +--- + +## ๐ŸŽ๏ธ 5. Parallel Processing with Workers +* **CPU Core Scaling**: Using `os.cpus().length` to determine how many Worker Threads to spawn, maximizing hardware performance. +* **Line-Boundary Splitting**: When processing large files, we split them into chunks but ensure we don't cut a log line in half (Divide and Conquer). +* **Stats Aggregation**: Each worker calculates "partial stats" and sends them back via `parentPort`. The main thread then "reduces" or merges these into the final result. + +--- + +## ๐ŸŒŠ 6. Advanced Stream Pipelines +* **`pipeline()` from `node:stream/promises`**: The most robust way to connect a `ReadStream`, a `Transform` (like CSV-to-JSON), and a `WriteStream`. It automatically manages memory (backpressure) and closes all streams if an error occurs. +* **Memory Efficiency**: By using Streams, the application can process 10GB files using only a few megabytes of RAM. \ No newline at end of file diff --git a/logs.txt b/logs.txt new file mode 100644 index 00000000..c1a05c0e --- /dev/null +++ b/logs.txt @@ -0,0 +1,1000 @@ +2026-03-16 WARN service 200 44 GET /api/data +2026-03-16 WARN service 200 15 GET /api/data +2026-03-16 INFO service 200 49 GET /api/data +2026-03-16 WARN service 200 70 GET /api/data +2026-03-16 WARN service 200 86 GET /api/data +2026-03-16 WARN service 200 12 GET /api/data +2026-03-16 ERROR service 200 67 GET /api/data +2026-03-16 ERROR service 200 67 GET /api/data +2026-03-16 INFO service 200 76 GET /api/data +2026-03-16 INFO service 200 54 GET /api/data +2026-03-16 WARN service 200 39 GET /api/data +2026-03-16 INFO service 200 2 GET /api/data +2026-03-16 ERROR service 200 20 GET /api/data +2026-03-16 INFO service 200 11 GET /api/data +2026-03-16 INFO service 200 10 GET /api/data +2026-03-16 ERROR service 200 29 GET /api/data +2026-03-16 INFO service 200 45 GET /api/data +2026-03-16 INFO service 200 23 GET /api/data +2026-03-16 INFO service 200 0 GET /api/data +2026-03-16 INFO service 200 2 GET /api/data +2026-03-16 ERROR service 200 46 GET /api/data +2026-03-16 WARN service 200 91 GET /api/data +2026-03-16 INFO service 200 19 GET /api/data +2026-03-16 WARN service 200 80 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 WARN service 200 38 GET /api/data +2026-03-16 WARN service 200 7 GET /api/data +2026-03-16 WARN service 200 22 GET /api/data +2026-03-16 INFO service 200 79 GET /api/data +2026-03-16 INFO service 200 80 GET /api/data +2026-03-16 ERROR service 200 96 GET /api/data +2026-03-16 ERROR service 200 69 GET /api/data +2026-03-16 ERROR service 200 41 GET /api/data +2026-03-16 ERROR service 200 55 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 WARN service 200 64 GET /api/data +2026-03-16 ERROR service 200 48 GET /api/data +2026-03-16 WARN service 200 29 GET /api/data +2026-03-16 WARN service 200 41 GET /api/data +2026-03-16 ERROR service 200 23 GET /api/data +2026-03-16 WARN service 200 86 GET /api/data +2026-03-16 WARN service 200 54 GET /api/data +2026-03-16 WARN service 200 3 GET /api/data +2026-03-16 WARN service 200 3 GET /api/data +2026-03-16 WARN service 200 66 GET /api/data +2026-03-16 ERROR service 200 90 GET /api/data +2026-03-16 INFO service 200 34 GET /api/data +2026-03-16 WARN service 200 94 GET /api/data +2026-03-16 INFO service 200 57 GET /api/data +2026-03-16 WARN service 200 59 GET /api/data +2026-03-16 WARN service 200 4 GET /api/data +2026-03-16 INFO service 200 3 GET /api/data +2026-03-16 ERROR service 200 18 GET /api/data +2026-03-16 WARN service 200 85 GET /api/data +2026-03-16 WARN service 200 31 GET /api/data +2026-03-16 ERROR service 200 97 GET /api/data +2026-03-16 ERROR service 200 28 GET /api/data +2026-03-16 WARN service 200 36 GET /api/data +2026-03-16 INFO service 200 17 GET /api/data +2026-03-16 INFO service 200 92 GET /api/data +2026-03-16 INFO service 200 13 GET /api/data +2026-03-16 INFO service 200 26 GET /api/data +2026-03-16 ERROR service 200 90 GET /api/data +2026-03-16 WARN service 200 21 GET /api/data +2026-03-16 ERROR service 200 70 GET /api/data +2026-03-16 ERROR service 200 35 GET /api/data +2026-03-16 ERROR service 200 22 GET /api/data +2026-03-16 INFO service 200 89 GET /api/data +2026-03-16 INFO service 200 29 GET /api/data +2026-03-16 ERROR service 200 77 GET /api/data +2026-03-16 INFO service 200 51 GET /api/data +2026-03-16 ERROR service 200 63 GET /api/data +2026-03-16 INFO service 200 74 GET /api/data +2026-03-16 INFO service 200 8 GET /api/data +2026-03-16 WARN service 200 78 GET /api/data +2026-03-16 WARN service 200 47 GET /api/data +2026-03-16 WARN service 200 76 GET /api/data +2026-03-16 ERROR service 200 25 GET /api/data +2026-03-16 ERROR service 200 63 GET /api/data +2026-03-16 INFO service 200 62 GET /api/data +2026-03-16 ERROR service 200 52 GET /api/data +2026-03-16 ERROR service 200 63 GET /api/data +2026-03-16 ERROR service 200 37 GET /api/data +2026-03-16 ERROR service 200 58 GET /api/data +2026-03-16 ERROR service 200 30 GET /api/data +2026-03-16 WARN service 200 38 GET /api/data +2026-03-16 WARN service 200 64 GET /api/data +2026-03-16 WARN service 200 29 GET /api/data +2026-03-16 INFO service 200 1 GET /api/data +2026-03-16 INFO service 200 91 GET /api/data +2026-03-16 INFO service 200 84 GET /api/data +2026-03-16 ERROR service 200 71 GET /api/data +2026-03-16 ERROR service 200 19 GET /api/data +2026-03-16 INFO service 200 75 GET /api/data +2026-03-16 INFO service 200 14 GET /api/data +2026-03-16 WARN service 200 27 GET /api/data +2026-03-16 WARN service 200 98 GET /api/data +2026-03-16 INFO service 200 19 GET /api/data +2026-03-16 ERROR service 200 97 GET /api/data +2026-03-16 ERROR service 200 26 GET /api/data +2026-03-16 WARN service 200 4 GET /api/data +2026-03-16 WARN service 200 47 GET /api/data +2026-03-16 ERROR service 200 12 GET /api/data +2026-03-16 INFO service 200 48 GET /api/data +2026-03-16 WARN service 200 86 GET /api/data +2026-03-16 WARN service 200 46 GET /api/data +2026-03-16 WARN service 200 48 GET /api/data +2026-03-16 WARN service 200 79 GET /api/data +2026-03-16 INFO service 200 31 GET /api/data +2026-03-16 ERROR service 200 72 GET /api/data +2026-03-16 INFO service 200 18 GET /api/data +2026-03-16 ERROR service 200 78 GET /api/data +2026-03-16 INFO service 200 72 GET /api/data +2026-03-16 INFO service 200 58 GET /api/data +2026-03-16 ERROR service 200 21 GET /api/data +2026-03-16 ERROR service 200 94 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 ERROR service 200 0 GET /api/data +2026-03-16 INFO service 200 5 GET /api/data +2026-03-16 INFO service 200 91 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 ERROR service 200 31 GET /api/data +2026-03-16 ERROR service 200 12 GET /api/data +2026-03-16 INFO service 200 4 GET /api/data +2026-03-16 WARN service 200 33 GET /api/data +2026-03-16 WARN service 200 20 GET /api/data +2026-03-16 ERROR service 200 36 GET /api/data +2026-03-16 ERROR service 200 23 GET /api/data +2026-03-16 ERROR service 200 11 GET /api/data +2026-03-16 WARN service 200 58 GET /api/data +2026-03-16 INFO service 200 17 GET /api/data +2026-03-16 INFO service 200 79 GET /api/data +2026-03-16 ERROR service 200 61 GET /api/data +2026-03-16 WARN service 200 69 GET /api/data +2026-03-16 WARN service 200 78 GET /api/data +2026-03-16 INFO service 200 22 GET /api/data +2026-03-16 WARN service 200 18 GET /api/data +2026-03-16 INFO service 200 7 GET /api/data +2026-03-16 WARN service 200 69 GET /api/data +2026-03-16 ERROR service 200 94 GET /api/data +2026-03-16 ERROR service 200 49 GET /api/data +2026-03-16 INFO service 200 40 GET /api/data +2026-03-16 ERROR service 200 79 GET /api/data +2026-03-16 ERROR service 200 77 GET /api/data +2026-03-16 ERROR service 200 55 GET /api/data +2026-03-16 ERROR service 200 77 GET /api/data +2026-03-16 WARN service 200 92 GET /api/data +2026-03-16 INFO service 200 45 GET /api/data +2026-03-16 ERROR service 200 44 GET /api/data +2026-03-16 WARN service 200 66 GET /api/data +2026-03-16 INFO service 200 70 GET /api/data +2026-03-16 INFO service 200 39 GET /api/data +2026-03-16 WARN service 200 48 GET /api/data +2026-03-16 WARN service 200 18 GET /api/data +2026-03-16 ERROR service 200 73 GET /api/data +2026-03-16 INFO service 200 89 GET /api/data +2026-03-16 INFO service 200 39 GET /api/data +2026-03-16 INFO service 200 69 GET /api/data +2026-03-16 ERROR service 200 40 GET /api/data +2026-03-16 WARN service 200 38 GET /api/data +2026-03-16 INFO service 200 65 GET /api/data +2026-03-16 ERROR service 200 19 GET /api/data +2026-03-16 WARN service 200 41 GET /api/data +2026-03-16 ERROR service 200 94 GET /api/data +2026-03-16 ERROR service 200 49 GET /api/data +2026-03-16 ERROR service 200 30 GET /api/data +2026-03-16 WARN service 200 24 GET /api/data +2026-03-16 ERROR service 200 71 GET /api/data +2026-03-16 WARN service 200 70 GET /api/data +2026-03-16 WARN service 200 32 GET /api/data +2026-03-16 ERROR service 200 84 GET /api/data +2026-03-16 ERROR service 200 72 GET /api/data +2026-03-16 INFO service 200 51 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 ERROR service 200 59 GET /api/data +2026-03-16 ERROR service 200 28 GET /api/data +2026-03-16 ERROR service 200 67 GET /api/data +2026-03-16 ERROR service 200 11 GET /api/data +2026-03-16 WARN service 200 66 GET /api/data +2026-03-16 WARN service 200 37 GET /api/data +2026-03-16 WARN service 200 17 GET /api/data +2026-03-16 ERROR service 200 16 GET /api/data +2026-03-16 ERROR service 200 72 GET /api/data +2026-03-16 INFO service 200 9 GET /api/data +2026-03-16 ERROR service 200 28 GET /api/data +2026-03-16 INFO service 200 10 GET /api/data +2026-03-16 WARN service 200 59 GET /api/data +2026-03-16 WARN service 200 42 GET /api/data +2026-03-16 ERROR service 200 51 GET /api/data +2026-03-16 WARN service 200 72 GET /api/data +2026-03-16 ERROR service 200 75 GET /api/data +2026-03-16 ERROR service 200 13 GET /api/data +2026-03-16 INFO service 200 81 GET /api/data +2026-03-16 INFO service 200 85 GET /api/data +2026-03-16 WARN service 200 3 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 INFO service 200 64 GET /api/data +2026-03-16 INFO service 200 65 GET /api/data +2026-03-16 ERROR service 200 38 GET /api/data +2026-03-16 ERROR service 200 8 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 INFO service 200 85 GET /api/data +2026-03-16 WARN service 200 83 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 ERROR service 200 3 GET /api/data +2026-03-16 INFO service 200 12 GET /api/data +2026-03-16 INFO service 200 99 GET /api/data +2026-03-16 ERROR service 200 89 GET /api/data +2026-03-16 ERROR service 200 25 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 INFO service 200 88 GET /api/data +2026-03-16 ERROR service 200 1 GET /api/data +2026-03-16 ERROR service 200 97 GET /api/data +2026-03-16 WARN service 200 57 GET /api/data +2026-03-16 INFO service 200 83 GET /api/data +2026-03-16 ERROR service 200 14 GET /api/data +2026-03-16 INFO service 200 97 GET /api/data +2026-03-16 ERROR service 200 98 GET /api/data +2026-03-16 WARN service 200 42 GET /api/data +2026-03-16 INFO service 200 60 GET /api/data +2026-03-16 INFO service 200 61 GET /api/data +2026-03-16 ERROR service 200 18 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 INFO service 200 93 GET /api/data +2026-03-16 ERROR service 200 94 GET /api/data +2026-03-16 ERROR service 200 24 GET /api/data +2026-03-16 WARN service 200 12 GET /api/data +2026-03-16 WARN service 200 79 GET /api/data +2026-03-16 INFO service 200 89 GET /api/data +2026-03-16 ERROR service 200 22 GET /api/data +2026-03-16 WARN service 200 79 GET /api/data +2026-03-16 INFO service 200 24 GET /api/data +2026-03-16 WARN service 200 2 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 INFO service 200 13 GET /api/data +2026-03-16 WARN service 200 68 GET /api/data +2026-03-16 INFO service 200 42 GET /api/data +2026-03-16 ERROR service 200 54 GET /api/data +2026-03-16 ERROR service 200 98 GET /api/data +2026-03-16 INFO service 200 5 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 INFO service 200 73 GET /api/data +2026-03-16 INFO service 200 29 GET /api/data +2026-03-16 ERROR service 200 74 GET /api/data +2026-03-16 WARN service 200 69 GET /api/data +2026-03-16 ERROR service 200 83 GET /api/data +2026-03-16 ERROR service 200 54 GET /api/data +2026-03-16 WARN service 200 83 GET /api/data +2026-03-16 WARN service 200 95 GET /api/data +2026-03-16 ERROR service 200 82 GET /api/data +2026-03-16 WARN service 200 6 GET /api/data +2026-03-16 INFO service 200 6 GET /api/data +2026-03-16 WARN service 200 34 GET /api/data +2026-03-16 INFO service 200 31 GET /api/data +2026-03-16 WARN service 200 29 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 WARN service 200 10 GET /api/data +2026-03-16 WARN service 200 34 GET /api/data +2026-03-16 ERROR service 200 81 GET /api/data +2026-03-16 WARN service 200 79 GET /api/data +2026-03-16 INFO service 200 37 GET /api/data +2026-03-16 ERROR service 200 33 GET /api/data +2026-03-16 INFO service 200 13 GET /api/data +2026-03-16 WARN service 200 46 GET /api/data +2026-03-16 INFO service 200 69 GET /api/data +2026-03-16 INFO service 200 4 GET /api/data +2026-03-16 INFO service 200 57 GET /api/data +2026-03-16 INFO service 200 31 GET /api/data +2026-03-16 INFO service 200 56 GET /api/data +2026-03-16 WARN service 200 78 GET /api/data +2026-03-16 INFO service 200 19 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 WARN service 200 96 GET /api/data +2026-03-16 ERROR service 200 60 GET /api/data +2026-03-16 ERROR service 200 49 GET /api/data +2026-03-16 ERROR service 200 54 GET /api/data +2026-03-16 INFO service 200 54 GET /api/data +2026-03-16 WARN service 200 33 GET /api/data +2026-03-16 INFO service 200 36 GET /api/data +2026-03-16 WARN service 200 65 GET /api/data +2026-03-16 INFO service 200 50 GET /api/data +2026-03-16 WARN service 200 9 GET /api/data +2026-03-16 INFO service 200 19 GET /api/data +2026-03-16 INFO service 200 51 GET /api/data +2026-03-16 INFO service 200 72 GET /api/data +2026-03-16 ERROR service 200 67 GET /api/data +2026-03-16 INFO service 200 16 GET /api/data +2026-03-16 WARN service 200 23 GET /api/data +2026-03-16 WARN service 200 42 GET /api/data +2026-03-16 WARN service 200 56 GET /api/data +2026-03-16 WARN service 200 93 GET /api/data +2026-03-16 ERROR service 200 27 GET /api/data +2026-03-16 INFO service 200 71 GET /api/data +2026-03-16 WARN service 200 89 GET /api/data +2026-03-16 WARN service 200 18 GET /api/data +2026-03-16 ERROR service 200 80 GET /api/data +2026-03-16 WARN service 200 80 GET /api/data +2026-03-16 INFO service 200 33 GET /api/data +2026-03-16 INFO service 200 85 GET /api/data +2026-03-16 ERROR service 200 56 GET /api/data +2026-03-16 WARN service 200 60 GET /api/data +2026-03-16 ERROR service 200 85 GET /api/data +2026-03-16 INFO service 200 35 GET /api/data +2026-03-16 ERROR service 200 83 GET /api/data +2026-03-16 INFO service 200 25 GET /api/data +2026-03-16 ERROR service 200 51 GET /api/data +2026-03-16 INFO service 200 48 GET /api/data +2026-03-16 INFO service 200 45 GET /api/data +2026-03-16 INFO service 200 56 GET /api/data +2026-03-16 WARN service 200 41 GET /api/data +2026-03-16 INFO service 200 70 GET /api/data +2026-03-16 ERROR service 200 44 GET /api/data +2026-03-16 WARN service 200 93 GET /api/data +2026-03-16 INFO service 200 86 GET /api/data +2026-03-16 WARN service 200 53 GET /api/data +2026-03-16 ERROR service 200 74 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 WARN service 200 96 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 INFO service 200 43 GET /api/data +2026-03-16 ERROR service 200 1 GET /api/data +2026-03-16 WARN service 200 80 GET /api/data +2026-03-16 WARN service 200 68 GET /api/data +2026-03-16 WARN service 200 95 GET /api/data +2026-03-16 WARN service 200 58 GET /api/data +2026-03-16 INFO service 200 56 GET /api/data +2026-03-16 WARN service 200 89 GET /api/data +2026-03-16 WARN service 200 12 GET /api/data +2026-03-16 ERROR service 200 69 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 INFO service 200 15 GET /api/data +2026-03-16 ERROR service 200 94 GET /api/data +2026-03-16 ERROR service 200 34 GET /api/data +2026-03-16 WARN service 200 15 GET /api/data +2026-03-16 INFO service 200 69 GET /api/data +2026-03-16 WARN service 200 0 GET /api/data +2026-03-16 INFO service 200 62 GET /api/data +2026-03-16 ERROR service 200 55 GET /api/data +2026-03-16 INFO service 200 38 GET /api/data +2026-03-16 WARN service 200 27 GET /api/data +2026-03-16 ERROR service 200 56 GET /api/data +2026-03-16 WARN service 200 64 GET /api/data +2026-03-16 ERROR service 200 85 GET /api/data +2026-03-16 INFO service 200 60 GET /api/data +2026-03-16 INFO service 200 49 GET /api/data +2026-03-16 WARN service 200 63 GET /api/data +2026-03-16 INFO service 200 44 GET /api/data +2026-03-16 ERROR service 200 79 GET /api/data +2026-03-16 WARN service 200 55 GET /api/data +2026-03-16 INFO service 200 87 GET /api/data +2026-03-16 WARN service 200 26 GET /api/data +2026-03-16 INFO service 200 2 GET /api/data +2026-03-16 ERROR service 200 47 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 INFO service 200 60 GET /api/data +2026-03-16 INFO service 200 42 GET /api/data +2026-03-16 ERROR service 200 31 GET /api/data +2026-03-16 INFO service 200 59 GET /api/data +2026-03-16 ERROR service 200 35 GET /api/data +2026-03-16 INFO service 200 95 GET /api/data +2026-03-16 INFO service 200 33 GET /api/data +2026-03-16 ERROR service 200 62 GET /api/data +2026-03-16 INFO service 200 65 GET /api/data +2026-03-16 WARN service 200 10 GET /api/data +2026-03-16 WARN service 200 31 GET /api/data +2026-03-16 INFO service 200 87 GET /api/data +2026-03-16 ERROR service 200 80 GET /api/data +2026-03-16 WARN service 200 8 GET /api/data +2026-03-16 INFO service 200 37 GET /api/data +2026-03-16 ERROR service 200 43 GET /api/data +2026-03-16 INFO service 200 98 GET /api/data +2026-03-16 INFO service 200 89 GET /api/data +2026-03-16 WARN service 200 51 GET /api/data +2026-03-16 WARN service 200 87 GET /api/data +2026-03-16 ERROR service 200 3 GET /api/data +2026-03-16 ERROR service 200 1 GET /api/data +2026-03-16 WARN service 200 35 GET /api/data +2026-03-16 INFO service 200 64 GET /api/data +2026-03-16 WARN service 200 55 GET /api/data +2026-03-16 WARN service 200 25 GET /api/data +2026-03-16 INFO service 200 5 GET /api/data +2026-03-16 WARN service 200 84 GET /api/data +2026-03-16 INFO service 200 65 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 ERROR service 200 54 GET /api/data +2026-03-16 ERROR service 200 74 GET /api/data +2026-03-16 WARN service 200 54 GET /api/data +2026-03-16 INFO service 200 46 GET /api/data +2026-03-16 ERROR service 200 64 GET /api/data +2026-03-16 ERROR service 200 83 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 WARN service 200 87 GET /api/data +2026-03-16 ERROR service 200 31 GET /api/data +2026-03-16 INFO service 200 45 GET /api/data +2026-03-16 WARN service 200 20 GET /api/data +2026-03-16 WARN service 200 39 GET /api/data +2026-03-16 WARN service 200 17 GET /api/data +2026-03-16 ERROR service 200 5 GET /api/data +2026-03-16 WARN service 200 24 GET /api/data +2026-03-16 ERROR service 200 12 GET /api/data +2026-03-16 INFO service 200 87 GET /api/data +2026-03-16 ERROR service 200 60 GET /api/data +2026-03-16 WARN service 200 75 GET /api/data +2026-03-16 INFO service 200 34 GET /api/data +2026-03-16 WARN service 200 84 GET /api/data +2026-03-16 WARN service 200 46 GET /api/data +2026-03-16 INFO service 200 99 GET /api/data +2026-03-16 INFO service 200 46 GET /api/data +2026-03-16 INFO service 200 25 GET /api/data +2026-03-16 WARN service 200 88 GET /api/data +2026-03-16 INFO service 200 92 GET /api/data +2026-03-16 ERROR service 200 71 GET /api/data +2026-03-16 ERROR service 200 52 GET /api/data +2026-03-16 WARN service 200 0 GET /api/data +2026-03-16 ERROR service 200 37 GET /api/data +2026-03-16 ERROR service 200 15 GET /api/data +2026-03-16 WARN service 200 55 GET /api/data +2026-03-16 INFO service 200 27 GET /api/data +2026-03-16 INFO service 200 8 GET /api/data +2026-03-16 WARN service 200 79 GET /api/data +2026-03-16 WARN service 200 2 GET /api/data +2026-03-16 WARN service 200 82 GET /api/data +2026-03-16 INFO service 200 15 GET /api/data +2026-03-16 WARN service 200 31 GET /api/data +2026-03-16 WARN service 200 68 GET /api/data +2026-03-16 WARN service 200 61 GET /api/data +2026-03-16 WARN service 200 27 GET /api/data +2026-03-16 ERROR service 200 79 GET /api/data +2026-03-16 WARN service 200 54 GET /api/data +2026-03-16 WARN service 200 7 GET /api/data +2026-03-16 WARN service 200 58 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 INFO service 200 50 GET /api/data +2026-03-16 ERROR service 200 91 GET /api/data +2026-03-16 WARN service 200 22 GET /api/data +2026-03-16 INFO service 200 7 GET /api/data +2026-03-16 ERROR service 200 79 GET /api/data +2026-03-16 WARN service 200 40 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 INFO service 200 18 GET /api/data +2026-03-16 INFO service 200 97 GET /api/data +2026-03-16 INFO service 200 37 GET /api/data +2026-03-16 WARN service 200 9 GET /api/data +2026-03-16 ERROR service 200 3 GET /api/data +2026-03-16 INFO service 200 43 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 WARN service 200 75 GET /api/data +2026-03-16 WARN service 200 57 GET /api/data +2026-03-16 ERROR service 200 59 GET /api/data +2026-03-16 WARN service 200 14 GET /api/data +2026-03-16 WARN service 200 5 GET /api/data +2026-03-16 WARN service 200 46 GET /api/data +2026-03-16 INFO service 200 41 GET /api/data +2026-03-16 ERROR service 200 74 GET /api/data +2026-03-16 WARN service 200 68 GET /api/data +2026-03-16 ERROR service 200 6 GET /api/data +2026-03-16 INFO service 200 91 GET /api/data +2026-03-16 ERROR service 200 24 GET /api/data +2026-03-16 WARN service 200 74 GET /api/data +2026-03-16 INFO service 200 45 GET /api/data +2026-03-16 WARN service 200 52 GET /api/data +2026-03-16 WARN service 200 88 GET /api/data +2026-03-16 INFO service 200 30 GET /api/data +2026-03-16 ERROR service 200 26 GET /api/data +2026-03-16 INFO service 200 68 GET /api/data +2026-03-16 WARN service 200 52 GET /api/data +2026-03-16 WARN service 200 63 GET /api/data +2026-03-16 ERROR service 200 5 GET /api/data +2026-03-16 INFO service 200 64 GET /api/data +2026-03-16 INFO service 200 93 GET /api/data +2026-03-16 ERROR service 200 64 GET /api/data +2026-03-16 ERROR service 200 44 GET /api/data +2026-03-16 INFO service 200 61 GET /api/data +2026-03-16 WARN service 200 91 GET /api/data +2026-03-16 INFO service 200 97 GET /api/data +2026-03-16 WARN service 200 94 GET /api/data +2026-03-16 ERROR service 200 73 GET /api/data +2026-03-16 ERROR service 200 92 GET /api/data +2026-03-16 INFO service 200 63 GET /api/data +2026-03-16 WARN service 200 42 GET /api/data +2026-03-16 INFO service 200 48 GET /api/data +2026-03-16 INFO service 200 25 GET /api/data +2026-03-16 INFO service 200 79 GET /api/data +2026-03-16 INFO service 200 45 GET /api/data +2026-03-16 ERROR service 200 91 GET /api/data +2026-03-16 ERROR service 200 28 GET /api/data +2026-03-16 ERROR service 200 53 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 WARN service 200 99 GET /api/data +2026-03-16 ERROR service 200 2 GET /api/data +2026-03-16 INFO service 200 29 GET /api/data +2026-03-16 ERROR service 200 62 GET /api/data +2026-03-16 INFO service 200 34 GET /api/data +2026-03-16 ERROR service 200 60 GET /api/data +2026-03-16 ERROR service 200 40 GET /api/data +2026-03-16 ERROR service 200 87 GET /api/data +2026-03-16 ERROR service 200 57 GET /api/data +2026-03-16 ERROR service 200 58 GET /api/data +2026-03-16 INFO service 200 20 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 WARN service 200 70 GET /api/data +2026-03-16 ERROR service 200 79 GET /api/data +2026-03-16 ERROR service 200 64 GET /api/data +2026-03-16 ERROR service 200 14 GET /api/data +2026-03-16 INFO service 200 5 GET /api/data +2026-03-16 WARN service 200 17 GET /api/data +2026-03-16 INFO service 200 53 GET /api/data +2026-03-16 INFO service 200 78 GET /api/data +2026-03-16 WARN service 200 86 GET /api/data +2026-03-16 INFO service 200 94 GET /api/data +2026-03-16 ERROR service 200 42 GET /api/data +2026-03-16 WARN service 200 29 GET /api/data +2026-03-16 INFO service 200 39 GET /api/data +2026-03-16 WARN service 200 92 GET /api/data +2026-03-16 WARN service 200 57 GET /api/data +2026-03-16 INFO service 200 94 GET /api/data +2026-03-16 ERROR service 200 81 GET /api/data +2026-03-16 INFO service 200 37 GET /api/data +2026-03-16 WARN service 200 96 GET /api/data +2026-03-16 ERROR service 200 53 GET /api/data +2026-03-16 ERROR service 200 69 GET /api/data +2026-03-16 WARN service 200 27 GET /api/data +2026-03-16 INFO service 200 59 GET /api/data +2026-03-16 WARN service 200 89 GET /api/data +2026-03-16 ERROR service 200 91 GET /api/data +2026-03-16 INFO service 200 23 GET /api/data +2026-03-16 INFO service 200 7 GET /api/data +2026-03-16 ERROR service 200 18 GET /api/data +2026-03-16 ERROR service 200 42 GET /api/data +2026-03-16 ERROR service 200 96 GET /api/data +2026-03-16 WARN service 200 33 GET /api/data +2026-03-16 WARN service 200 69 GET /api/data +2026-03-16 INFO service 200 99 GET /api/data +2026-03-16 ERROR service 200 77 GET /api/data +2026-03-16 WARN service 200 86 GET /api/data +2026-03-16 ERROR service 200 37 GET /api/data +2026-03-16 ERROR service 200 38 GET /api/data +2026-03-16 INFO service 200 10 GET /api/data +2026-03-16 INFO service 200 93 GET /api/data +2026-03-16 WARN service 200 35 GET /api/data +2026-03-16 WARN service 200 9 GET /api/data +2026-03-16 ERROR service 200 13 GET /api/data +2026-03-16 WARN service 200 70 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 INFO service 200 1 GET /api/data +2026-03-16 ERROR service 200 12 GET /api/data +2026-03-16 WARN service 200 82 GET /api/data +2026-03-16 INFO service 200 52 GET /api/data +2026-03-16 ERROR service 200 34 GET /api/data +2026-03-16 ERROR service 200 97 GET /api/data +2026-03-16 INFO service 200 39 GET /api/data +2026-03-16 ERROR service 200 98 GET /api/data +2026-03-16 INFO service 200 27 GET /api/data +2026-03-16 ERROR service 200 60 GET /api/data +2026-03-16 ERROR service 200 42 GET /api/data +2026-03-16 WARN service 200 5 GET /api/data +2026-03-16 ERROR service 200 49 GET /api/data +2026-03-16 WARN service 200 48 GET /api/data +2026-03-16 ERROR service 200 52 GET /api/data +2026-03-16 INFO service 200 82 GET /api/data +2026-03-16 INFO service 200 86 GET /api/data +2026-03-16 ERROR service 200 48 GET /api/data +2026-03-16 INFO service 200 9 GET /api/data +2026-03-16 INFO service 200 92 GET /api/data +2026-03-16 ERROR service 200 0 GET /api/data +2026-03-16 WARN service 200 40 GET /api/data +2026-03-16 WARN service 200 99 GET /api/data +2026-03-16 ERROR service 200 21 GET /api/data +2026-03-16 WARN service 200 17 GET /api/data +2026-03-16 WARN service 200 72 GET /api/data +2026-03-16 ERROR service 200 26 GET /api/data +2026-03-16 WARN service 200 71 GET /api/data +2026-03-16 WARN service 200 20 GET /api/data +2026-03-16 ERROR service 200 58 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 WARN service 200 29 GET /api/data +2026-03-16 INFO service 200 25 GET /api/data +2026-03-16 ERROR service 200 15 GET /api/data +2026-03-16 INFO service 200 80 GET /api/data +2026-03-16 INFO service 200 34 GET /api/data +2026-03-16 INFO service 200 25 GET /api/data +2026-03-16 ERROR service 200 58 GET /api/data +2026-03-16 INFO service 200 10 GET /api/data +2026-03-16 INFO service 200 3 GET /api/data +2026-03-16 INFO service 200 50 GET /api/data +2026-03-16 INFO service 200 51 GET /api/data +2026-03-16 ERROR service 200 54 GET /api/data +2026-03-16 WARN service 200 88 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 ERROR service 200 53 GET /api/data +2026-03-16 INFO service 200 31 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 ERROR service 200 8 GET /api/data +2026-03-16 INFO service 200 20 GET /api/data +2026-03-16 WARN service 200 10 GET /api/data +2026-03-16 ERROR service 200 6 GET /api/data +2026-03-16 ERROR service 200 81 GET /api/data +2026-03-16 ERROR service 200 39 GET /api/data +2026-03-16 ERROR service 200 14 GET /api/data +2026-03-16 ERROR service 200 26 GET /api/data +2026-03-16 ERROR service 200 56 GET /api/data +2026-03-16 INFO service 200 43 GET /api/data +2026-03-16 ERROR service 200 74 GET /api/data +2026-03-16 ERROR service 200 99 GET /api/data +2026-03-16 INFO service 200 13 GET /api/data +2026-03-16 INFO service 200 85 GET /api/data +2026-03-16 WARN service 200 66 GET /api/data +2026-03-16 ERROR service 200 30 GET /api/data +2026-03-16 INFO service 200 39 GET /api/data +2026-03-16 WARN service 200 34 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 WARN service 200 89 GET /api/data +2026-03-16 ERROR service 200 33 GET /api/data +2026-03-16 WARN service 200 48 GET /api/data +2026-03-16 ERROR service 200 19 GET /api/data +2026-03-16 ERROR service 200 80 GET /api/data +2026-03-16 ERROR service 200 28 GET /api/data +2026-03-16 ERROR service 200 23 GET /api/data +2026-03-16 ERROR service 200 27 GET /api/data +2026-03-16 INFO service 200 63 GET /api/data +2026-03-16 WARN service 200 6 GET /api/data +2026-03-16 WARN service 200 50 GET /api/data +2026-03-16 INFO service 200 22 GET /api/data +2026-03-16 ERROR service 200 72 GET /api/data +2026-03-16 ERROR service 200 22 GET /api/data +2026-03-16 ERROR service 200 28 GET /api/data +2026-03-16 ERROR service 200 30 GET /api/data +2026-03-16 WARN service 200 59 GET /api/data +2026-03-16 WARN service 200 83 GET /api/data +2026-03-16 WARN service 200 22 GET /api/data +2026-03-16 WARN service 200 54 GET /api/data +2026-03-16 ERROR service 200 17 GET /api/data +2026-03-16 ERROR service 200 4 GET /api/data +2026-03-16 INFO service 200 32 GET /api/data +2026-03-16 ERROR service 200 18 GET /api/data +2026-03-16 INFO service 200 40 GET /api/data +2026-03-16 WARN service 200 71 GET /api/data +2026-03-16 ERROR service 200 3 GET /api/data +2026-03-16 ERROR service 200 30 GET /api/data +2026-03-16 INFO service 200 71 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 ERROR service 200 17 GET /api/data +2026-03-16 INFO service 200 88 GET /api/data +2026-03-16 ERROR service 200 90 GET /api/data +2026-03-16 ERROR service 200 0 GET /api/data +2026-03-16 ERROR service 200 9 GET /api/data +2026-03-16 ERROR service 200 54 GET /api/data +2026-03-16 ERROR service 200 74 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 ERROR service 200 80 GET /api/data +2026-03-16 ERROR service 200 94 GET /api/data +2026-03-16 INFO service 200 10 GET /api/data +2026-03-16 WARN service 200 2 GET /api/data +2026-03-16 INFO service 200 87 GET /api/data +2026-03-16 INFO service 200 38 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 ERROR service 200 19 GET /api/data +2026-03-16 INFO service 200 22 GET /api/data +2026-03-16 ERROR service 200 38 GET /api/data +2026-03-16 ERROR service 200 65 GET /api/data +2026-03-16 ERROR service 200 20 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 INFO service 200 12 GET /api/data +2026-03-16 WARN service 200 59 GET /api/data +2026-03-16 WARN service 200 47 GET /api/data +2026-03-16 INFO service 200 71 GET /api/data +2026-03-16 INFO service 200 22 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 ERROR service 200 74 GET /api/data +2026-03-16 INFO service 200 89 GET /api/data +2026-03-16 WARN service 200 60 GET /api/data +2026-03-16 INFO service 200 43 GET /api/data +2026-03-16 WARN service 200 17 GET /api/data +2026-03-16 ERROR service 200 31 GET /api/data +2026-03-16 ERROR service 200 61 GET /api/data +2026-03-16 INFO service 200 4 GET /api/data +2026-03-16 ERROR service 200 45 GET /api/data +2026-03-16 INFO service 200 34 GET /api/data +2026-03-16 INFO service 200 10 GET /api/data +2026-03-16 ERROR service 200 55 GET /api/data +2026-03-16 INFO service 200 20 GET /api/data +2026-03-16 ERROR service 200 23 GET /api/data +2026-03-16 ERROR service 200 58 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 WARN service 200 31 GET /api/data +2026-03-16 WARN service 200 83 GET /api/data +2026-03-16 ERROR service 200 87 GET /api/data +2026-03-16 INFO service 200 49 GET /api/data +2026-03-16 ERROR service 200 29 GET /api/data +2026-03-16 WARN service 200 9 GET /api/data +2026-03-16 INFO service 200 72 GET /api/data +2026-03-16 WARN service 200 23 GET /api/data +2026-03-16 WARN service 200 48 GET /api/data +2026-03-16 ERROR service 200 50 GET /api/data +2026-03-16 WARN service 200 53 GET /api/data +2026-03-16 ERROR service 200 28 GET /api/data +2026-03-16 WARN service 200 17 GET /api/data +2026-03-16 INFO service 200 93 GET /api/data +2026-03-16 ERROR service 200 13 GET /api/data +2026-03-16 WARN service 200 28 GET /api/data +2026-03-16 INFO service 200 6 GET /api/data +2026-03-16 ERROR service 200 22 GET /api/data +2026-03-16 ERROR service 200 84 GET /api/data +2026-03-16 INFO service 200 84 GET /api/data +2026-03-16 INFO service 200 21 GET /api/data +2026-03-16 ERROR service 200 55 GET /api/data +2026-03-16 WARN service 200 55 GET /api/data +2026-03-16 WARN service 200 21 GET /api/data +2026-03-16 ERROR service 200 89 GET /api/data +2026-03-16 ERROR service 200 70 GET /api/data +2026-03-16 WARN service 200 66 GET /api/data +2026-03-16 ERROR service 200 5 GET /api/data +2026-03-16 WARN service 200 22 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 WARN service 200 3 GET /api/data +2026-03-16 ERROR service 200 71 GET /api/data +2026-03-16 WARN service 200 14 GET /api/data +2026-03-16 WARN service 200 67 GET /api/data +2026-03-16 INFO service 200 6 GET /api/data +2026-03-16 INFO service 200 88 GET /api/data +2026-03-16 INFO service 200 33 GET /api/data +2026-03-16 INFO service 200 5 GET /api/data +2026-03-16 INFO service 200 49 GET /api/data +2026-03-16 INFO service 200 74 GET /api/data +2026-03-16 WARN service 200 52 GET /api/data +2026-03-16 WARN service 200 68 GET /api/data +2026-03-16 WARN service 200 72 GET /api/data +2026-03-16 ERROR service 200 53 GET /api/data +2026-03-16 ERROR service 200 57 GET /api/data +2026-03-16 ERROR service 200 17 GET /api/data +2026-03-16 INFO service 200 46 GET /api/data +2026-03-16 INFO service 200 66 GET /api/data +2026-03-16 WARN service 200 82 GET /api/data +2026-03-16 WARN service 200 79 GET /api/data +2026-03-16 ERROR service 200 92 GET /api/data +2026-03-16 ERROR service 200 86 GET /api/data +2026-03-16 WARN service 200 79 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 ERROR service 200 88 GET /api/data +2026-03-16 INFO service 200 90 GET /api/data +2026-03-16 WARN service 200 33 GET /api/data +2026-03-16 WARN service 200 62 GET /api/data +2026-03-16 INFO service 200 76 GET /api/data +2026-03-16 ERROR service 200 91 GET /api/data +2026-03-16 WARN service 200 85 GET /api/data +2026-03-16 ERROR service 200 3 GET /api/data +2026-03-16 INFO service 200 14 GET /api/data +2026-03-16 ERROR service 200 17 GET /api/data +2026-03-16 WARN service 200 8 GET /api/data +2026-03-16 INFO service 200 14 GET /api/data +2026-03-16 ERROR service 200 17 GET /api/data +2026-03-16 WARN service 200 16 GET /api/data +2026-03-16 ERROR service 200 54 GET /api/data +2026-03-16 ERROR service 200 37 GET /api/data +2026-03-16 INFO service 200 86 GET /api/data +2026-03-16 ERROR service 200 78 GET /api/data +2026-03-16 INFO service 200 80 GET /api/data +2026-03-16 ERROR service 200 66 GET /api/data +2026-03-16 ERROR service 200 9 GET /api/data +2026-03-16 INFO service 200 15 GET /api/data +2026-03-16 WARN service 200 22 GET /api/data +2026-03-16 ERROR service 200 3 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 WARN service 200 93 GET /api/data +2026-03-16 INFO service 200 54 GET /api/data +2026-03-16 INFO service 200 49 GET /api/data +2026-03-16 INFO service 200 18 GET /api/data +2026-03-16 WARN service 200 16 GET /api/data +2026-03-16 ERROR service 200 34 GET /api/data +2026-03-16 ERROR service 200 91 GET /api/data +2026-03-16 WARN service 200 83 GET /api/data +2026-03-16 INFO service 200 98 GET /api/data +2026-03-16 ERROR service 200 17 GET /api/data +2026-03-16 INFO service 200 71 GET /api/data +2026-03-16 ERROR service 200 75 GET /api/data +2026-03-16 INFO service 200 60 GET /api/data +2026-03-16 WARN service 200 20 GET /api/data +2026-03-16 WARN service 200 56 GET /api/data +2026-03-16 WARN service 200 36 GET /api/data +2026-03-16 ERROR service 200 4 GET /api/data +2026-03-16 INFO service 200 41 GET /api/data +2026-03-16 ERROR service 200 10 GET /api/data +2026-03-16 INFO service 200 70 GET /api/data +2026-03-16 INFO service 200 74 GET /api/data +2026-03-16 WARN service 200 52 GET /api/data +2026-03-16 INFO service 200 20 GET /api/data +2026-03-16 INFO service 200 5 GET /api/data +2026-03-16 WARN service 200 65 GET /api/data +2026-03-16 ERROR service 200 36 GET /api/data +2026-03-16 WARN service 200 78 GET /api/data +2026-03-16 ERROR service 200 99 GET /api/data +2026-03-16 INFO service 200 89 GET /api/data +2026-03-16 INFO service 200 22 GET /api/data +2026-03-16 INFO service 200 17 GET /api/data +2026-03-16 INFO service 200 30 GET /api/data +2026-03-16 WARN service 200 53 GET /api/data +2026-03-16 WARN service 200 44 GET /api/data +2026-03-16 WARN service 200 42 GET /api/data +2026-03-16 WARN service 200 18 GET /api/data +2026-03-16 WARN service 200 40 GET /api/data +2026-03-16 WARN service 200 94 GET /api/data +2026-03-16 INFO service 200 1 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 WARN service 200 5 GET /api/data +2026-03-16 INFO service 200 84 GET /api/data +2026-03-16 ERROR service 200 90 GET /api/data +2026-03-16 WARN service 200 49 GET /api/data +2026-03-16 INFO service 200 43 GET /api/data +2026-03-16 ERROR service 200 82 GET /api/data +2026-03-16 INFO service 200 16 GET /api/data +2026-03-16 WARN service 200 96 GET /api/data +2026-03-16 INFO service 200 86 GET /api/data +2026-03-16 ERROR service 200 95 GET /api/data +2026-03-16 ERROR service 200 40 GET /api/data +2026-03-16 ERROR service 200 26 GET /api/data +2026-03-16 ERROR service 200 42 GET /api/data +2026-03-16 INFO service 200 20 GET /api/data +2026-03-16 WARN service 200 88 GET /api/data +2026-03-16 INFO service 200 97 GET /api/data +2026-03-16 WARN service 200 70 GET /api/data +2026-03-16 WARN service 200 46 GET /api/data +2026-03-16 WARN service 200 52 GET /api/data +2026-03-16 INFO service 200 11 GET /api/data +2026-03-16 INFO service 200 11 GET /api/data +2026-03-16 INFO service 200 85 GET /api/data +2026-03-16 INFO service 200 49 GET /api/data +2026-03-16 INFO service 200 92 GET /api/data +2026-03-16 INFO service 200 89 GET /api/data +2026-03-16 WARN service 200 69 GET /api/data +2026-03-16 ERROR service 200 78 GET /api/data +2026-03-16 INFO service 200 48 GET /api/data +2026-03-16 ERROR service 200 25 GET /api/data +2026-03-16 INFO service 200 45 GET /api/data +2026-03-16 ERROR service 200 25 GET /api/data +2026-03-16 WARN service 200 0 GET /api/data +2026-03-16 INFO service 200 42 GET /api/data +2026-03-16 INFO service 200 14 GET /api/data +2026-03-16 WARN service 200 80 GET /api/data +2026-03-16 INFO service 200 35 GET /api/data +2026-03-16 ERROR service 200 7 GET /api/data +2026-03-16 ERROR service 200 79 GET /api/data +2026-03-16 ERROR service 200 51 GET /api/data +2026-03-16 ERROR service 200 92 GET /api/data +2026-03-16 ERROR service 200 37 GET /api/data +2026-03-16 INFO service 200 66 GET /api/data +2026-03-16 INFO service 200 88 GET /api/data +2026-03-16 WARN service 200 2 GET /api/data +2026-03-16 WARN service 200 87 GET /api/data +2026-03-16 WARN service 200 49 GET /api/data +2026-03-16 INFO service 200 27 GET /api/data +2026-03-16 ERROR service 200 53 GET /api/data +2026-03-16 INFO service 200 37 GET /api/data +2026-03-16 WARN service 200 36 GET /api/data +2026-03-16 WARN service 200 95 GET /api/data +2026-03-16 WARN service 200 80 GET /api/data +2026-03-16 INFO service 200 73 GET /api/data +2026-03-16 INFO service 200 62 GET /api/data +2026-03-16 WARN service 200 84 GET /api/data +2026-03-16 WARN service 200 4 GET /api/data +2026-03-16 INFO service 200 36 GET /api/data +2026-03-16 ERROR service 200 22 GET /api/data +2026-03-16 INFO service 200 3 GET /api/data +2026-03-16 WARN service 200 2 GET /api/data +2026-03-16 INFO service 200 58 GET /api/data +2026-03-16 ERROR service 200 89 GET /api/data +2026-03-16 WARN service 200 85 GET /api/data +2026-03-16 INFO service 200 60 GET /api/data +2026-03-16 ERROR service 200 20 GET /api/data +2026-03-16 WARN service 200 12 GET /api/data +2026-03-16 ERROR service 200 82 GET /api/data +2026-03-16 ERROR service 200 69 GET /api/data +2026-03-16 ERROR service 200 71 GET /api/data +2026-03-16 WARN service 200 91 GET /api/data +2026-03-16 ERROR service 200 64 GET /api/data +2026-03-16 INFO service 200 80 GET /api/data +2026-03-16 WARN service 200 94 GET /api/data +2026-03-16 ERROR service 200 19 GET /api/data +2026-03-16 WARN service 200 32 GET /api/data +2026-03-16 INFO service 200 99 GET /api/data +2026-03-16 INFO service 200 51 GET /api/data +2026-03-16 INFO service 200 95 GET /api/data +2026-03-16 ERROR service 200 60 GET /api/data +2026-03-16 ERROR service 200 11 GET /api/data +2026-03-16 ERROR service 200 56 GET /api/data +2026-03-16 WARN service 200 63 GET /api/data +2026-03-16 WARN service 200 22 GET /api/data +2026-03-16 WARN service 200 56 GET /api/data +2026-03-16 ERROR service 200 70 GET /api/data +2026-03-16 INFO service 200 75 GET /api/data +2026-03-16 INFO service 200 14 GET /api/data +2026-03-16 WARN service 200 95 GET /api/data +2026-03-16 WARN service 200 34 GET /api/data +2026-03-16 INFO service 200 16 GET /api/data +2026-03-16 WARN service 200 80 GET /api/data +2026-03-16 ERROR service 200 12 GET /api/data +2026-03-16 INFO service 200 28 GET /api/data +2026-03-16 WARN service 200 66 GET /api/data +2026-03-16 INFO service 200 25 GET /api/data +2026-03-16 WARN service 200 43 GET /api/data +2026-03-16 INFO service 200 84 GET /api/data +2026-03-16 INFO service 200 29 GET /api/data +2026-03-16 WARN service 200 75 GET /api/data +2026-03-16 ERROR service 200 53 GET /api/data +2026-03-16 ERROR service 200 51 GET /api/data +2026-03-16 INFO service 200 59 GET /api/data +2026-03-16 INFO service 200 54 GET /api/data +2026-03-16 ERROR service 200 98 GET /api/data +2026-03-16 INFO service 200 51 GET /api/data +2026-03-16 INFO service 200 24 GET /api/data +2026-03-16 WARN service 200 84 GET /api/data +2026-03-16 ERROR service 200 91 GET /api/data +2026-03-16 INFO service 200 91 GET /api/data +2026-03-16 ERROR service 200 78 GET /api/data +2026-03-16 ERROR service 200 18 GET /api/data +2026-03-16 WARN service 200 26 GET /api/data +2026-03-16 ERROR service 200 87 GET /api/data +2026-03-16 ERROR service 200 12 GET /api/data +2026-03-16 INFO service 200 9 GET /api/data +2026-03-16 ERROR service 200 6 GET /api/data +2026-03-16 WARN service 200 7 GET /api/data +2026-03-16 ERROR service 200 40 GET /api/data +2026-03-16 ERROR service 200 9 GET /api/data +2026-03-16 WARN service 200 26 GET /api/data +2026-03-16 INFO service 200 73 GET /api/data +2026-03-16 INFO service 200 3 GET /api/data +2026-03-16 WARN service 200 57 GET /api/data +2026-03-16 INFO service 200 91 GET /api/data +2026-03-16 ERROR service 200 27 GET /api/data +2026-03-16 INFO service 200 19 GET /api/data +2026-03-16 INFO service 200 40 GET /api/data +2026-03-16 INFO service 200 39 GET /api/data +2026-03-16 WARN service 200 36 GET /api/data +2026-03-16 ERROR service 200 87 GET /api/data +2026-03-16 WARN service 200 46 GET /api/data +2026-03-16 INFO service 200 55 GET /api/data +2026-03-16 WARN service 200 96 GET /api/data +2026-03-16 ERROR service 200 31 GET /api/data +2026-03-16 ERROR service 200 37 GET /api/data +2026-03-16 ERROR service 200 56 GET /api/data +2026-03-16 ERROR service 200 94 GET /api/data +2026-03-16 ERROR service 200 49 GET /api/data +2026-03-16 WARN service 200 10 GET /api/data +2026-03-16 ERROR service 200 56 GET /api/data +2026-03-16 WARN service 200 8 GET /api/data +2026-03-16 INFO service 200 88 GET /api/data +2026-03-16 ERROR service 200 33 GET /api/data +2026-03-16 WARN service 200 49 GET /api/data +2026-03-16 ERROR service 200 52 GET /api/data +2026-03-16 INFO service 200 29 GET /api/data +2026-03-16 ERROR service 200 11 GET /api/data +2026-03-16 WARN service 200 96 GET /api/data +2026-03-16 INFO service 200 24 GET /api/data +2026-03-16 ERROR service 200 30 GET /api/data +2026-03-16 INFO service 200 81 GET /api/data +2026-03-16 WARN service 200 90 GET /api/data +2026-03-16 WARN service 200 1 GET /api/data +2026-03-16 INFO service 200 58 GET /api/data +2026-03-16 ERROR service 200 48 GET /api/data +2026-03-16 WARN service 200 26 GET /api/data +2026-03-16 WARN service 200 53 GET /api/data +2026-03-16 WARN service 200 10 GET /api/data +2026-03-16 INFO service 200 40 GET /api/data +2026-03-16 ERROR service 200 12 GET /api/data +2026-03-16 WARN service 200 90 GET /api/data +2026-03-16 ERROR service 200 75 GET /api/data +2026-03-16 ERROR service 200 57 GET /api/data +2026-03-16 WARN service 200 99 GET /api/data +2026-03-16 ERROR service 200 46 GET /api/data +2026-03-16 INFO service 200 63 GET /api/data +2026-03-16 INFO service 200 27 GET /api/data +2026-03-16 WARN service 200 88 GET /api/data +2026-03-16 ERROR service 200 17 GET /api/data +2026-03-16 INFO service 200 7 GET /api/data +2026-03-16 WARN service 200 65 GET /api/data +2026-03-16 INFO service 200 60 GET /api/data +2026-03-16 ERROR service 200 68 GET /api/data +2026-03-16 ERROR service 200 51 GET /api/data +2026-03-16 ERROR service 200 82 GET /api/data +2026-03-16 ERROR service 200 22 GET /api/data +2026-03-16 INFO service 200 20 GET /api/data +2026-03-16 WARN service 200 75 GET /api/data +2026-03-16 WARN service 200 45 GET /api/data +2026-03-16 ERROR service 200 13 GET /api/data +2026-03-16 INFO service 200 96 GET /api/data +2026-03-16 WARN service 200 9 GET /api/data +2026-03-16 INFO service 200 51 GET /api/data +2026-03-16 INFO service 200 54 GET /api/data +2026-03-16 INFO service 200 50 GET /api/data +2026-03-16 ERROR service 200 69 GET /api/data +2026-03-16 INFO service 200 94 GET /api/data +2026-03-16 WARN service 200 52 GET /api/data +2026-03-16 WARN service 200 48 GET /api/data +2026-03-16 ERROR service 200 41 GET /api/data +2026-03-16 INFO service 200 92 GET /api/data +2026-03-16 WARN service 200 33 GET /api/data +2026-03-16 WARN service 200 38 GET /api/data diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..755c365b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "node-nodejs-fundamentals", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-nodejs-fundamentals", + "version": "1.0.0", + "license": "ISC", + "engines": { + "node": ">=24.10.0", + "npm": ">=10.9.2" + } + } + } +} diff --git a/package.json.md5 b/package.json.md5 new file mode 100644 index 00000000..5a605c8b --- /dev/null +++ b/package.json.md5 @@ -0,0 +1 @@ +774c180863f9133cc987e793e9f91877 \ No newline at end of file diff --git a/restored.csv b/restored.csv new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/restored.csv @@ -0,0 +1 @@ + diff --git a/secret.enc b/secret.enc new file mode 100644 index 00000000..4680579a Binary files /dev/null and b/secret.enc differ diff --git a/src/cli/interactive.js b/src/cli/interactive.js index d0e3e0d9..fe46a97e 100644 --- a/src/cli/interactive.js +++ b/src/cli/interactive.js @@ -1,8 +1,66 @@ +import readline from "node:readline"; + +// Write your code here +// Use readline module for interactive CLI +// Support commands: uptime, cwd, date, exit +// Handle Ctrl+C and unknown commands + const interactive = () => { - // Write your code here - // Use readline module for interactive CLI - // Support commands: uptime, cwd, date, exit - // Handle Ctrl+C and unknown commands + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + // Requirement: Display a prompt "> " + prompt: "> ", + }); + + // Initial prompt display + rl.prompt(); + + rl.on("line", (line) => { + const command = line.trim().toLowerCase(); + + switch (command) { + case "uptime": + // Requirement: prints process uptime in seconds (e.g. Uptime: 12.34s) + process.stdout.write(`Uptime: ${process.uptime().toFixed(2)}s\n`); + break; + + case "cwd": + // Requirement: prints the current working directory + process.stdout.write(`${process.cwd()}\n`); + break; + + case "date": + // Requirement: prints the current date and time in ISO format + process.stdout.write(`${new Date().toISOString()}\n`); + break; + + case "exit": + // Requirement: prints "Goodbye!" and terminates + process.stdout.write("Goodbye!\n"); + rl.close(); + break; + + default: + // Requirement: On unknown command, print "Unknown command" + process.stdout.write("Unknown command\n"); + break; + } + + if (command !== "exit") { + rl.prompt(); + } + }); + + // Requirement: On Ctrl+C or end of input, print "Goodbye!" and exit + rl.on("SIGINT", () => { + process.stdout.write("\nGoodbye!\n"); + rl.close(); + }); + + rl.on("close", () => { + process.exit(0); + }); }; interactive(); diff --git a/src/cli/progress.js b/src/cli/progress.js index 3e060763..a7abad9d 100644 --- a/src/cli/progress.js +++ b/src/cli/progress.js @@ -1,8 +1,58 @@ +// Write your code here +// Simulate progress bar from 0% to 100% over ~5 seconds +// Update in place using \r every 100ms +// Format: [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ ] 67% +// const progress = () => { - // Write your code here - // Simulate progress bar from 0% to 100% over ~5 seconds - // Update in place using \r every 100ms - // Format: [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ ] 67% + const args = process.argv; + + // Helper function to get argument values or return a default + const getArg = (flag, defaultValue) => { + const index = args.indexOf(flag); + return index !== -1 && args[index + 1] ? args[index + 1] : defaultValue; + }; + + // Parsing arguments with their default values + const duration = parseInt(getArg("--duration", "5000")); + const interval = parseInt(getArg("--interval", "100")); + const length = parseInt(getArg("--length", "30")); + const hexColor = getArg("--color", null); + + // Helper to convert #RRGGBB to ANSI escape code for 24-bit color + const getAnsiColor = (hex) => { + if (!hex || !/^#[0-9A-Fa-f]{6}$/.test(hex)) return ""; + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `\x1b[38;2;${r};${g};${b}m`; + }; + + const colorStart = getAnsiColor(hexColor); + const colorReset = "\x1b[0m"; + + let percentage = 0; + const increment = 100 / (duration / interval); + + const timer = setInterval(() => { + percentage += increment; + if (percentage > 100) percentage = 100; + + const filledLength = Math.floor((percentage / 100) * length); + const emptyLength = length - filledLength; + + const filledPart = "โ–ˆ".repeat(filledLength); + const emptyPart = " ".repeat(emptyLength); + + // Building the bar: Apply color only to the filled part if color exists + const formattedBar = `${colorStart}${filledPart}${colorReset}${emptyPart}`; + + process.stdout.write(`\r[${formattedBar}] ${Math.round(percentage)}%`); + + if (percentage >= 100) { + clearInterval(timer); + process.stdout.write("\nDone!\n"); + } + }, interval); }; progress(); diff --git a/src/commands/count.js b/src/commands/count.js new file mode 100644 index 00000000..3ffaf8fa --- /dev/null +++ b/src/commands/count.js @@ -0,0 +1,33 @@ +import fs from "node:fs"; +import { resolvePath } from "../utils/pathResolver.js"; + +export const countFile = async (currentDir, inputArg) => { + if (!inputArg) { + console.log("Invalid input"); + return; + } + + const filePath = resolvePath(currentDir, inputArg); + let lines = 0; + let words = 0; + let characters = 0; + + const readStream = fs.createReadStream(filePath, { encoding: "utf8" }); + + try { + for await (const chunk of readStream) { + characters += chunk.length; + lines += (chunk.match(/\n/g) || []).length; + words += chunk + .trim() + .split(/\s+/) + .filter((w) => w.length > 0).length; + } + + console.log(`Lines: ${lines}`); + console.log(`Words: ${words}`); + console.log(`Characters: ${characters}`); + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/commands/csvToJson.js b/src/commands/csvToJson.js new file mode 100644 index 00000000..e74cc95a --- /dev/null +++ b/src/commands/csvToJson.js @@ -0,0 +1,66 @@ +import fs from "node:fs"; +import { pipeline } from "node:stream/promises"; +import { Transform } from "node:stream"; + +const createCsvTransform = () => { + let headers = null; + let isFirstLine = true; + + return new Transform({ + transform(chunk, encoding, callback) { + const content = chunk.toString(); + const lines = content.split(/\r?\n/).filter((line) => line.trim() !== ""); + + if (isFirstLine && lines.length > 0) { + // Get headers from the first line + headers = lines + .shift() + .split(",") + .map((h) => h.trim()); + isFirstLine = false; + this.push("[\n"); + } + + const jsonRows = lines.map((line, index) => { + const values = line.split(","); + const obj = {}; + headers.forEach((header, i) => { + obj[header] = values[i]?.trim() || ""; + }); + + // Add comma except for the very first object in the array + const prefix = index === 0 && this._isNewArray ? "" : ",\n"; + this._isNewArray = false; + return JSON.stringify(obj, null, 2); + }); + + // Join the rows with commas + if (jsonRows.length > 0) { + // Logic to handle commas between chunks + const output = (this.notFirstPush ? ",\n" : "") + jsonRows.join(",\n"); + this.push(output); + this.notFirstPush = true; + } + callback(); + }, + flush(callback) { + this.push("\n]"); + callback(); + }, + }); +}; + +export const csvToJson = async (inputPath, outputPath) => { + try { + const readStream = fs.createReadStream(inputPath); + const writeStream = fs.createWriteStream(outputPath); + const transformStream = createCsvTransform(); + transformStream.notFirstPush = false; + transformStream._isNewArray = true; + + await pipeline(readStream, transformStream, writeStream); + console.log("Conversion successful!"); + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/commands/decrypt.js b/src/commands/decrypt.js new file mode 100644 index 00000000..3eda07f9 --- /dev/null +++ b/src/commands/decrypt.js @@ -0,0 +1,48 @@ +import fs from "node:fs"; +import { pipeline } from "node:stream/promises"; +import { scrypt, createDecipheriv } from "node:crypto"; +import { promisify } from "node:util"; +import { resolvePath } from "../utils/pathResolver.js"; + +const scryptAsync = promisify(scrypt); + +export const decryptFile = async (currentDir, args) => { + const { input, output, password } = args; + if (!input || !output || !password) return console.log("Invalid input"); + + try { + const inputPath = resolvePath(currentDir, input); + const outputPath = resolvePath(currentDir, output); + + // Read headers (salt + iv = 28 bytes) manually first + const fd = await fs.promises.open(inputPath, "r"); + const headerBuffer = Buffer.alloc(28); + await fd.read(headerBuffer, 0, 28, 0); + + const salt = headerBuffer.slice(0, 16); + const iv = headerBuffer.slice(16, 28); + + // Get Auth Tag from the last 16 bytes + const stats = await fd.stat(); + const tagBuffer = Buffer.alloc(16); + await fd.read(tagBuffer, 0, 16, stats.size - 16); + await fd.close(); + + const key = await scryptAsync(password, salt, 32); + const decipher = createDecipheriv("aes-256-gcm", key, iv); + decipher.setAuthTag(tagBuffer); + + // Decrypt the middle part using streams + const readStream = fs.createReadStream(inputPath, { + start: 28, + end: stats.size - 17, + }); + const writeStream = fs.createWriteStream(outputPath); + + await pipeline(readStream, decipher, writeStream); + + console.log("Decryption successful!"); + } catch (err) { + console.log("Operation failed"); + } +}; diff --git a/src/commands/encrypt.js b/src/commands/encrypt.js new file mode 100644 index 00000000..a5354f90 --- /dev/null +++ b/src/commands/encrypt.js @@ -0,0 +1,39 @@ +import { createReadStream, createWriteStream } from "node:fs"; +import { pipeline } from "node:stream/promises"; +import { scrypt, randomBytes, createCipheriv } from "node:crypto"; +import { promisify } from "node:util"; +import { resolvePath } from "../utils/pathResolver.js"; + +const scryptAsync = promisify(scrypt); + +export const encryptFile = async (currentDir, args) => { + const { input, output, password } = args; + if (!input || !output || !password) return console.log("Invalid input"); + + try { + const inputPath = resolvePath(currentDir, input); + const outputPath = resolvePath(currentDir, output); + + const salt = randomBytes(16); + const iv = randomBytes(12); + const key = await scryptAsync(password, salt, 32); + + const cipher = createCipheriv("aes-256-gcm", key, iv); + const readStream = createReadStream(inputPath); + const writeStream = createWriteStream(outputPath); + + writeStream.write(salt); + writeStream.write(iv); + + await pipeline(readStream, cipher, writeStream); + + const authTag = cipher.getAuthTag(); + const tagStream = createWriteStream(outputPath, { flags: "a" }); + tagStream.write(authTag); + tagStream.end(); + + console.log("Encryption successful!"); + } catch (err) { + console.log("Operation failed"); + } +}; diff --git a/src/commands/hash.js b/src/commands/hash.js new file mode 100644 index 00000000..34543e7e --- /dev/null +++ b/src/commands/hash.js @@ -0,0 +1,31 @@ +import fs from "node:fs"; +import { createHash } from "node:crypto"; +import { pipeline } from "node:stream/promises"; +import { resolvePath } from "../utils/pathResolver.js"; + +export const calculateHash = async (currentDir, args) => { + const { input, algorithm = "sha256", save } = args; + + if (!input) { + console.log("Invalid input"); + return; + } + + const inputPath = resolvePath(currentDir, input); + const hash = createHash(algorithm); + + try { + const readStream = fs.createReadStream(inputPath); + await pipeline(readStream, hash); + const result = hash.digest("hex"); + + console.log(`${algorithm}: ${result}`); + + if (save) { + const outputPath = `${inputPath}.${algorithm}`; + await fs.promises.writeFile(outputPath, result); + } + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/commands/hashCompare.js b/src/commands/hashCompare.js new file mode 100644 index 00000000..04556e4f --- /dev/null +++ b/src/commands/hashCompare.js @@ -0,0 +1,31 @@ +import fs from "node:fs/promises"; +import { createReadStream } from "node:fs"; +import { createHash } from "node:crypto"; +import { pipeline } from "node:stream/promises"; +import { resolvePath } from "../utils/pathResolver.js"; + +export const hashCompare = async (currentDir, args) => { + const { input, hash: hashFile, algorithm = "sha256" } = args; + + if (!input || !hashFile) { + console.log("Invalid input"); + return; + } + + try { + const inputPath = resolvePath(currentDir, input); + const hashFilePath = resolvePath(currentDir, hashFile); + const hasher = createHash(algorithm); + await pipeline(createReadStream(inputPath), hasher); + const actualHash = hasher.digest("hex"); + const expectedHash = (await fs.readFile(hashFilePath, "utf8")).trim(); + + if (actualHash.toLowerCase() === expectedHash.toLowerCase()) { + console.log("OK"); + } else { + console.log("MISMATCH"); + } + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/commands/jsonToCsv.js b/src/commands/jsonToCsv.js new file mode 100644 index 00000000..2fecc9b3 --- /dev/null +++ b/src/commands/jsonToCsv.js @@ -0,0 +1,35 @@ +import fs from "node:fs"; +import { resolvePath } from "../utils/pathResolver.js"; + +export const jsonToCsv = async (currentDir, input, output) => { + if (!input || !output) return console.log("Invalid input"); + + const inputPath = resolvePath(currentDir, input); + const outputPath = resolvePath(currentDir, output); + + try { + const content = await fs.promises.readFile(inputPath, "utf8"); + const data = JSON.parse(content); + + if (!Array.isArray(data) || data.length === 0) { + throw new Error("Invalid data"); + } + + const headers = Object.keys(data[0]); + const writeStream = fs.createWriteStream(outputPath); + + // Write headers + writeStream.write(headers.join(",") + "\n"); + + // Stream the rows to the file + for (const obj of data) { + const row = headers.map((header) => obj[header] ?? "").join(","); + writeStream.write(row + "\n"); + } + + writeStream.end(); + console.log("Conversion successful!"); + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/commands/logStats.js b/src/commands/logStats.js new file mode 100644 index 00000000..0f43223b --- /dev/null +++ b/src/commands/logStats.js @@ -0,0 +1,84 @@ +import fs from "node:fs/promises"; +import { Worker } from "node:worker_threads"; +import { cpus } from "node:os"; +import { resolvePath } from "../utils/pathResolver.js"; + +export const logStats = async (currentDir, input, output) => { + try { + const inputPath = resolvePath(currentDir, input); + const outputPath = resolvePath(currentDir, output); + + const content = await fs.readFile(inputPath, "utf8"); + const lines = content.split("\n"); + + const numWorkers = cpus().length; + const chunkSize = Math.ceil(lines.length / numWorkers); + const workerPromises = []; + + const finalStats = { + total: 0, + levels: { INFO: 0, WARN: 0, ERROR: 0 }, + status: { "2xx": 0, "3xx": 0, "4xx": 0, "5xx": 0 }, + paths: {}, + responseTimeSum: 0, + }; + + for (let i = 0; i < numWorkers; i++) { + const chunk = lines.slice(i * chunkSize, (i + 1) * chunkSize); + + const worker = new Worker( + new URL("../workers/logWorker.js", import.meta.url) + ); + + workerPromises.push( + new Promise((resolve) => { + worker.on("message", (partialStats) => { + finalStats.total += partialStats.total; + finalStats.responseTimeSum += partialStats.responseTimeSum; + + Object.keys(partialStats.levels).forEach( + (l) => (finalStats.levels[l] += partialStats.levels[l]) + ); + Object.keys(partialStats.status).forEach( + (s) => (finalStats.status[s] += partialStats.status[s]) + ); + Object.keys(partialStats.paths).forEach((p) => { + finalStats.paths[p] = + (finalStats.paths[p] || 0) + partialStats.paths[p]; + }); + + worker.terminate(); + resolve(); + }); + + worker.postMessage({ lines: chunk }); + }) + ); + } + + await Promise.all(workerPromises); + + const topPaths = Object.entries(finalStats.paths) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5) + .map(([path, count]) => ({ path, count })); + + const result = { + total: finalStats.total, + levels: finalStats.levels, + status: finalStats.status, + topPaths, + avgResponseTimeMs: + finalStats.total > 0 + ? parseFloat( + (finalStats.responseTimeSum / finalStats.total).toFixed(2) + ) + : 0, + }; + + await fs.writeFile(outputPath, JSON.stringify(result, null, 2)); + console.log("Log analysis complete!"); + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/cp/execCommand.js b/src/cp/execCommand.js index 34a89c8d..6001efa8 100644 --- a/src/cp/execCommand.js +++ b/src/cp/execCommand.js @@ -1,10 +1,50 @@ +import { spawn } from "node:child_process"; + +// Write your code here +// Take command from CLI argument +// Spawn child process +// Pipe child stdout/stderr to parent stdout/stderr +// Pass environment variables +// Exit with same code as child + const execCommand = () => { - // Write your code here - // Take command from CLI argument - // Spawn child process - // Pipe child stdout/stderr to parent stdout/stderr - // Pass environment variables - // Exit with same code as child + // 1. Get the command string from the arguments + const commandArg = process.argv[2]; + + if (!commandArg) { + process.stderr.write("Please provide a command string, (e.g., 'ls -la')\n"); + process.exit(1); + } + + // 2. Parse the command and its arguments + // Example: "ls -la" -> command: "ls", args: ["-la"] + const [command, ...args] = commandArg.split(" "); + + // 3. Spawn the child process + // We pass 'process.env' so the child has the same environment variables + const child = spawn(command, args, { + env: process.env, + shell: true, // Use shell to correctly parse strings on Mac/Windows + }); + + // 4. Pipe the outputs + // This connects the child's mouth (stdout) to the parent's mouth (stdout) + child.stdout.pipe(process.stdout); + child.stderr.pipe(process.stderr); + + // + + // 5. Handle the exit + child.on("close", (code) => { + // Requirement: parent process exits with the same exit code + process.exit(code); + }); + + // Handle errors if the command doesn't exist + child.on("error", (err) => { + process.stderr.write(`Failed to start process: ${err.message}\n`); + process.exit(1); + }); }; execCommand(); diff --git a/src/fs/findByExt.js b/src/fs/findByExt.js index 24f06cb8..863e077c 100644 --- a/src/fs/findByExt.js +++ b/src/fs/findByExt.js @@ -1,7 +1,56 @@ +import { readdir, stat } from "node:fs/promises"; +import { resolve, join, relative, extname } from "node:path"; + +// Write your code here +// Recursively find all files with specific extension +// Parse --ext CLI argument (default: .txt) + const findByExt = async () => { - // Write your code here - // Recursively find all files with specific extension - // Parse --ext CLI argument (default: .txt) + try { + const workspacePath = resolve(process.cwd(), "workspace"); + + // Pre-validation: Ensure the workspace directory exists + try { + await stat(workspacePath); + } catch { + throw new Error("FS operation failed"); + } + + const args = process.argv; + const extIndex = args.indexOf("--ext"); + let targetExt = ".txt"; + + if (extIndex !== -1 && args[extIndex + 1]) { + const providedValue = args[extIndex + 1]; + targetExt = providedValue.startsWith(".") + ? providedValue + : `.${providedValue}`; + } + + const results = []; + + const scan = async (currentPath) => { + const folderContents = await readdir(currentPath); + for (const item of folderContents) { + const fullPath = join(currentPath, item); + const itemStat = await stat(fullPath); + + if (itemStat.isFile()) { + if (extname(fullPath) === targetExt) { + results.push(relative(workspacePath, fullPath)); + } + } else if (itemStat.isDirectory()) { + await scan(fullPath); + } + } + }; + + await scan(workspacePath); + results.sort(); + results.forEach((filePath) => process.stdout.write(`${filePath}\n`)); + } catch (error) { + throw new Error("FS operation failed"); + } }; await findByExt(); diff --git a/src/fs/merge.js b/src/fs/merge.js index cb8e0d8f..fc6d0051 100644 --- a/src/fs/merge.js +++ b/src/fs/merge.js @@ -1,8 +1,52 @@ +import { readdir, readFile, writeFile, stat } from "node:fs/promises"; +import { resolve, join, extname } from "node:path"; + +// Write your code here +// Default: read all .txt files from workspace/parts in alphabetical order +// Optional: support --files filename1,filename2,... to merge specific files in provided order +// Concatenate content and write to workspace/merged.txt + const merge = async () => { - // Write your code here - // Default: read all .txt files from workspace/parts in alphabetical order - // Optional: support --files filename1,filename2,... to merge specific files in provided order - // Concatenate content and write to workspace/merged.txt + try { + const workspacePath = resolve(process.cwd(), "workspace"); + const partsPath = join(workspacePath, "parts"); + const outputPath = join(workspacePath, "merged.txt"); + + try { + await stat(partsPath); + } catch { + throw new Error("FS operation failed"); + } + + const args = process.argv; + const filesIndex = args.indexOf("--files"); + let filesToMerge = []; + + if (filesIndex !== -1 && args[filesIndex + 1]) { + filesToMerge = args[filesIndex + 1].split(","); + } else { + const allFiles = await readdir(partsPath); + filesToMerge = allFiles.filter((file) => extname(file) === ".txt").sort(); + } + + if (filesToMerge.length === 0) throw new Error("FS operation failed"); + + let combinedContent = ""; + for (const fileName of filesToMerge) { + const filePath = join(partsPath, fileName); + try { + const content = await readFile(filePath, "utf-8"); + combinedContent += content; + } catch { + throw new Error("FS operation failed"); + } + } + + await writeFile(outputPath, combinedContent); + process.stdout.write("merged.txt\n"); + } catch (error) { + throw new Error("FS operation failed"); + } }; await merge(); diff --git a/src/fs/restore.js b/src/fs/restore.js index 96ae1ffb..5baf5abb 100644 --- a/src/fs/restore.js +++ b/src/fs/restore.js @@ -1,8 +1,50 @@ +import { readFile, writeFile, mkdir, stat } from "node:fs/promises"; +import { resolve, join, dirname } from "node:path"; + +// Write your code here +// Read snapshot.json +// Treat snapshot.rootPath as metadata only +// Recreate directory/file structure in workspace_restored + const restore = async () => { - // Write your code here - // Read snapshot.json - // Treat snapshot.rootPath as metadata only - // Recreate directory/file structure in workspace_restored + const snapshotPath = resolve(process.cwd(), "snapshot.json"); + const restorePath = resolve(process.cwd(), "workspace_restored"); + + try { + // Validate snapshot exists + try { + await stat(snapshotPath); + } catch { + throw new Error("FS operation failed"); + } + + // Ensure destination does not exist + try { + await stat(restorePath); + throw new Error("FS operation failed"); + } catch (error) { + if (error.message === "FS operation failed") throw error; + } + + const rawData = await readFile(snapshotPath, "utf-8"); + const { entries } = JSON.parse(rawData); + + await mkdir(restorePath); + + for (const entry of entries) { + const entryPath = join(restorePath, entry.path); + + if (entry.type === "directory") { + await mkdir(entryPath, { recursive: true }); + } else { + await mkdir(dirname(entryPath), { recursive: true }); + const fileBuffer = Buffer.from(entry.content, "base64"); + await writeFile(entryPath, fileBuffer); + } + } + } catch (error) { + throw new Error("FS operation failed"); + } }; await restore(); diff --git a/src/fs/snapshot.js b/src/fs/snapshot.js index 050103d3..703af1d3 100644 --- a/src/fs/snapshot.js +++ b/src/fs/snapshot.js @@ -1,9 +1,63 @@ +import { readdir, stat, writeFile, readFile } from "node:fs/promises"; +import { resolve, join, relative } from "node:path"; + +// Write your code here +// Recursively scan workspace directory +// Write snapshot.json with: +// - rootPath: absolute path to workspace +// - entries: flat array of relative paths and metadata + const snapshot = async () => { - // Write your code here - // Recursively scan workspace directory - // Write snapshot.json with: - // - rootPath: absolute path to workspace - // - entries: flat array of relative paths and metadata + try { + const workspacePath = resolve(process.cwd(), "workspace"); + const entries = []; + + try { + await stat(workspacePath); + } catch { + throw new Error("FS operation failed"); + } + + const scan = async (currentDir) => { + const names = await readdir(currentDir); + + for (const name of names) { + const fullPath = join(currentDir, name); + const fileStat = await stat(fullPath); + const relativePath = relative(workspacePath, fullPath); + + if (fileStat.isFile()) { + const content = await readFile(fullPath); + entries.push({ + path: relativePath, + type: "file", + size: fileStat.size, + content: content.toString("base64"), + }); + } else if (fileStat.isDirectory()) { + entries.push({ + path: relativePath, + type: "directory", + }); + await scan(fullPath); + } + } + }; + + await scan(workspacePath); + + const finalResult = { + rootPath: workspacePath, + entries: entries, + }; + + await writeFile("snapshot.json", JSON.stringify(finalResult, null, 2)); + + process.stdout.write(`the rootPath is: ${workspacePath}\n`); + process.stdout.write("Snapshot created successfully!\n"); + } catch (error) { + throw new Error("FS operation failed"); + } }; await snapshot(); diff --git a/src/hash/verify.js b/src/hash/verify.js index 7f1e8961..90ad6509 100644 --- a/src/hash/verify.js +++ b/src/hash/verify.js @@ -1,8 +1,62 @@ +import { readFile, stat } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import { createReadStream } from "node:fs"; +import { resolve, join } from "node:path"; +import { pipeline } from "node:stream/promises"; + +// Write your code here +// Read checksums.json +// Calculate SHA256 hash using Streams API +// Print result: filename โ€” OK/FAIL + const verify = async () => { - // Write your code here - // Read checksums.json - // Calculate SHA256 hash using Streams API - // Print result: filename โ€” OK/FAIL + const checksumsPath = resolve(process.cwd(), "checksums.json"); + const workspacePath = resolve(process.cwd(), "workspace"); + + try { + // 1. Initial validation: Ensure checksums.json exists + try { + await stat(checksumsPath); + } catch { + // Required error message if the file is missing + throw new Error("FS operation failed"); + } + + // 2. Load the expected hashes from the JSON file + const rawData = await readFile(checksumsPath, "utf-8"); + const checksums = JSON.parse(rawData); + + // 3. Process each file entry in the JSON + for (const [fileName, expectedHash] of Object.entries(checksums)) { + const filePath = join(workspacePath, fileName); + + try { + // We create a Hash transform stream for SHA256 + const hash = createHash("sha256"); + // We open a readable stream for the file + const fileStream = createReadStream(filePath); + + // + // Pipeline connects the file to the hash calculator and waits for it to finish + await pipeline(fileStream, hash); + + // Calculate the final hexadecimal hash string + const actualHash = hash.digest("hex"); + + // 4. Verification and Output + const isMatch = actualHash === expectedHash; + const status = isMatch ? "OK" : "FAIL"; + + process.stdout.write(`${fileName} โ€” ${status}\n`); + } catch { + // If a file listed in the JSON doesn't exist, we print FAIL + process.stdout.write(`${fileName} โ€” FAIL\n`); + } + } + } catch (error) { + // Standard error for missing JSON or other critical FS failures + throw new Error("FS operation failed"); + } }; await verify(); diff --git a/src/main.js b/src/main.js new file mode 100644 index 00000000..bce42038 --- /dev/null +++ b/src/main.js @@ -0,0 +1,50 @@ +import readline from "node:readline"; +import { homedir } from "node:os"; +import { handleCommand } from "./repl.js"; + +const startCLI = () => { + const state = { + currentDir: homedir(), + }; + + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: true, + }); + + console.log("Welcome to Data Processing CLI!"); + console.log(`You are currently in ${state.currentDir}`); + + const showPrompt = () => { + process.stdout.write(`\nYou are currently in ${state.currentDir}\n> `); + }; + + showPrompt(); + + rl.on("line", async (line) => { + const input = line.trim(); + + if (input === ".exit") { + rl.close(); + return; + } + + if (input) { + await handleCommand(input, state); + } + + showPrompt(); + }); + + rl.on("close", () => { + console.log("Thank you for using Data Processing CLI, goodbye!"); + process.exit(0); + }); + + rl.on("SIGINT", () => { + rl.close(); + }); +}; + +startCLI(); diff --git a/src/modules/dynamic.js b/src/modules/dynamic.js index 008ca387..26f43ccd 100644 --- a/src/modules/dynamic.js +++ b/src/modules/dynamic.js @@ -1,9 +1,41 @@ +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; + +// Write your code here +// Accept plugin name as CLI argument +// Dynamically import plugin from plugins/ directory +// Call run() function and print result +// Handle missing plugin case + const dynamic = async () => { - // Write your code here - // Accept plugin name as CLI argument - // Dynamically import plugin from plugins/ directory - // Call run() function and print result - // Handle missing plugin case + // We take the plugin name from the command line (e.g., node dynamic.js uppercase) + const pluginName = process.argv[2]; + + // If no name is provided, we just exit + if (!pluginName) { + process.exit(1); + } + + try { + // 1. Build the absolute path to the plugin file inside the plugins/ folder + const pluginPath = resolve("src", "modules", "plugins", `${pluginName}.js`); + + // 2. Convert the path to a URL format (required for dynamic imports on some systems) + const pluginURL = pathToFileURL(pluginPath).href; + + // 3. The Magic: Dynamic Import. This loads the file only when we need it. + const pluginModule = await import(pluginURL); + + // 4. Execution: Every plugin must have a run() function + const result = pluginModule.run(); + + // 5. Output: Print the string returned by the plugin + process.stdout.write(`${result}\n`); + } catch (error) { + // Requirement: If the file doesn't exist, print "Plugin not found" and exit with code 1 + process.stdout.write("Plugin not found\n"); + process.exit(1); + } }; await dynamic(); diff --git a/src/modules/plugins/uppercase.js b/src/modules/plugins/uppercase.js index b8b0c6b7..e64440fd 100644 --- a/src/modules/plugins/uppercase.js +++ b/src/modules/plugins/uppercase.js @@ -1,3 +1,3 @@ export const run = () => { - return 'HELLO WORLD'; + return "HELLO WORLD"; }; diff --git a/src/navigation.js b/src/navigation.js new file mode 100644 index 00000000..cac47fdd --- /dev/null +++ b/src/navigation.js @@ -0,0 +1,47 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +// Move up one level +export const goUp = (currentDir) => { + return path.resolve(currentDir, ".."); +}; + +// Change to a new folder +export const changeDir = async (currentDir, targetPath) => { + const newPath = path.resolve(currentDir, targetPath); + try { + const stats = await fs.stat(newPath); + if (stats.isDirectory()) { + return newPath; + } + console.log("Operation failed"); + return currentDir; + } catch (error) { + console.log("Operation failed"); + return currentDir; + } +}; + +// List folders and files +export const listDirectory = async (currentDir) => { + try { + const entries = await fs.readdir(currentDir, { withFileTypes: true }); + + // Sort: Folders first, then Files + const sorted = entries.sort((a, b) => { + if (a.isDirectory() && !b.isDirectory()) return -1; + if (!a.isDirectory() && b.isDirectory()) return 1; + return a.name.localeCompare(b.name); + }); + + // Print table format + console.table( + sorted.map((entry) => ({ + Name: entry.name, + Type: entry.isDirectory() ? "folder" : "file", + })) + ); + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/repl.js b/src/repl.js new file mode 100644 index 00000000..6b19a810 --- /dev/null +++ b/src/repl.js @@ -0,0 +1,75 @@ +import { goUp, changeDir, listDirectory } from "./navigation.js"; +import { parseArgs } from "./utils/argParser.js"; +import { countFile } from "./commands/count.js"; +import { csvToJson } from "./commands/csvToJson.js"; +import { jsonToCsv } from "./commands/jsonToCsv.js"; +import { calculateHash } from "./commands/hash.js"; +import { hashCompare } from "./commands/hashCompare.js"; +import { logStats } from "./commands/logStats.js"; +import { encryptFile } from "./commands/encrypt.js"; +import { decryptFile } from "./commands/decrypt.js"; + +export const handleCommand = async (input, state) => { + const parts = input.trim().split(/\s+/); + const command = parts[0]; + const parsedArgs = parseArgs(parts.slice(1)); + + try { + switch (command) { + case "up": + state.currentDir = goUp(state.currentDir); + break; + case "cd": + if (parts[1]) + state.currentDir = await changeDir(state.currentDir, parts[1]); + else console.log("Invalid input"); + break; + case "ls": + await listDirectory(state.currentDir); + break; + case "count": + if (parsedArgs.input) + await countFile(state.currentDir, parsedArgs.input); + else console.log("Invalid input"); + break; + case "csv-to-json": + if (parsedArgs.input && parsedArgs.output) + await csvToJson(parsedArgs.input, parsedArgs.output); + else console.log("Invalid input"); + break; + case "json-to-csv": + if (parsedArgs.input && parsedArgs.output) + await jsonToCsv(parsedArgs.input, parsedArgs.output); + else console.log("Invalid input"); + break; + case "hash": + if (parsedArgs.input) await calculateHash(state.currentDir, parsedArgs); + else console.log("Invalid input"); + break; + case "hash-compare": + if (parsedArgs.input && parsedArgs.hash) + await hashCompare(state.currentDir, parsedArgs); + else console.log("Invalid input"); + break; + case "encrypt": + if (parsedArgs.input && parsedArgs.output && parsedArgs.password) + await encryptFile(state.currentDir, parsedArgs); + else console.log("Invalid input"); + break; + case "decrypt": + if (parsedArgs.input && parsedArgs.output && parsedArgs.password) + await decryptFile(state.currentDir, parsedArgs); + else console.log("Invalid input"); + break; + case "log-stats": + if (parsedArgs.input && parsedArgs.output) + await logStats(state.currentDir, parsedArgs.input, parsedArgs.output); + else console.log("Invalid input"); + break; + default: + console.log("Invalid input"); + } + } catch (error) { + console.log("Operation failed"); + } +}; diff --git a/src/streams/filter.js b/src/streams/filter.js index 3868ab46..4d362cab 100644 --- a/src/streams/filter.js +++ b/src/streams/filter.js @@ -1,9 +1,64 @@ -const filter = () => { - // Write your code here - // Read from process.stdin - // Filter lines by --pattern CLI argument - // Use Transform Stream - // Write to process.stdout +import { Transform } from "node:stream"; +import { pipeline } from "node:stream/promises"; + +// Write your code here +// Read from process.stdin +// Filter lines by --pattern CLI argument +// Use Transform Stream +// Write to process.stdout + +const filter = async () => { + const args = process.argv; + const patternIndex = args.indexOf("--pattern"); + + // Requirement: Get the pattern or default to an empty string (shows everything) + const pattern = + patternIndex !== -1 && args[patternIndex + 1] ? args[patternIndex + 1] : ""; + + let remainingData = ""; + + const filterTransform = new Transform({ + /** + * transform: Checks each line of the incoming chunk against the pattern. + */ + transform(chunk, encoding, callback) { + const data = remainingData + chunk.toString(); + const lines = data.split(/\r?\n/); + + // Save the last partial line for the next chunk + remainingData = lines.pop(); + + // We filter only the lines that include our pattern + const filteredOutput = lines + .filter((line) => line.includes(pattern)) + .join("\n"); + + // Only push data if we found matches + if (filteredOutput.length > 0) { + this.push(filteredOutput + "\n"); + } + + callback(); + }, + + /** + * flush: Processes the final piece of data when the stream closes. + */ + flush(callback) { + if (remainingData.length > 0 && remainingData.includes(pattern)) { + this.push(remainingData + "\n"); + } + callback(); + }, + }); + + try { + // Pipeline connects keyboard input -> our filter -> terminal output + await pipeline(process.stdin, filterTransform, process.stdout); + } catch (error) { + process.stderr.write("Stream filtering failed!\n"); + process.exit(1); + } }; -filter(); +await filter(); diff --git a/src/streams/lineNumberer.js b/src/streams/lineNumberer.js index 579d662e..2fa82fe2 100644 --- a/src/streams/lineNumberer.js +++ b/src/streams/lineNumberer.js @@ -1,8 +1,59 @@ -const lineNumberer = () => { - // Write your code here - // Read from process.stdin - // Use Transform Stream to prepend line numbers - // Write to process.stdout +import { Transform } from "node:stream"; +import { pipeline } from "node:stream/promises"; +// Write your code here +// Read from process.stdin +// Use Transform Stream to prepend line numbers +// Write to process.stdout + +const lineNumberer = async () => { + let lineNumber = 1; + let remainingData = ""; + + const addNumbersTransform = new Transform({ + /** + * The transform method processes each chunk of data. + */ + transform(chunk, encoding, callback) { + // Convert chunk to string and prepend any leftover data from the last chunk + const data = remainingData + chunk.toString(); + const lines = data.split(/\r?\n/); + + // Keep the last element (it might be an incomplete line) + remainingData = lines.pop(); + + // Process complete lines + const output = lines + .map((line) => `${lineNumber++} | ${line}`) + .join("\n"); + + // Push the transformed data to the next stage of the pipeline + if (output.length > 0) { + this.push(output + "\n"); + } + + callback(); + }, + + /** + * flush is called when the input stream ends. + */ + flush(callback) { + // If there's any text left without a final newline, process it now + if (remainingData.length > 0) { + this.push(`${lineNumber++} | ${remainingData}\n`); + } + callback(); + }, + }); + + try { + // Pipeline connects stdin -> our transform -> stdout + await pipeline(process.stdin, addNumbersTransform, process.stdout); + } catch (error) { + // Standard error handling for streams + process.stderr.write("Stream operation failed!\n"); + process.exit(1); + } }; -lineNumberer(); +await lineNumberer(); diff --git a/src/streams/split.js b/src/streams/split.js index f8f814fa..bda1efa9 100644 --- a/src/streams/split.js +++ b/src/streams/split.js @@ -1,8 +1,76 @@ +import { createReadStream, createWriteStream } from "node:fs"; +import { stat } from "node:fs/promises"; +import { resolve } from "node:path"; +import { createInterface } from "node:readline"; + +// Write your code here +// Read source.txt using Readable Stream +// Split into chunk_1.txt, chunk_2.txt, etc. +// Each chunk max N lines (--lines CLI argument, default: 10) + const split = async () => { - // Write your code here - // Read source.txt using Readable Stream - // Split into chunk_1.txt, chunk_2.txt, etc. - // Each chunk max N lines (--lines CLI argument, default: 10) + const sourcePath = resolve(process.cwd(), "source.txt"); + const args = process.argv; + const linesIndex = args.indexOf("--lines"); + + // Requirement: Default to 10 lines if --lines is not provided + const linesPerChunk = + linesIndex !== -1 && args[linesIndex + 1] + ? parseInt(args[linesIndex + 1]) + : 10; + + try { + // 1. Initial validation: Check if source.txt exists + await stat(sourcePath); + + // 2. Create a Readable Stream to read source.txt + const readable = createReadStream(sourcePath); + + // 3. Use readline interface to process the stream line by line + const rl = createInterface({ + input: readable, + crlfDelay: Infinity, + }); + + let currentLineCount = 0; + let chunkIndex = 1; + let currentWriteStream = null; + + /** + * Function to generate the next chunk's Writable Stream + */ + const getNewWriteStream = (index) => { + return createWriteStream(resolve(process.cwd(), `chunk_${index}.txt`)); + }; + + // 4. Listen to each line from the source file + for await (const line of rl) { + // If we don't have an active file or reached the limit, start a new one + if (!currentWriteStream || currentLineCount >= linesPerChunk) { + if (currentWriteStream) { + currentWriteStream.end(); + } + currentLineCount = 0; + currentWriteStream = getNewWriteStream(chunkIndex++); + } + + // Write the line followed by a newline character + currentWriteStream.write(`${line}\n`); + currentLineCount++; + } + + // 5. Cleanup: Close the last stream if it exists + if (currentWriteStream) { + currentWriteStream.end(); + process.stdout.write( + `File split successfully into ${chunkIndex - 1} chunks! \n` + ); + } + } catch (error) { + // If source.txt is missing or any FS error occurs + process.stderr.write("FS operation failed\n"); + process.exit(1); + } }; await split(); diff --git a/src/utils/argParser.js b/src/utils/argParser.js new file mode 100644 index 00000000..3be30672 --- /dev/null +++ b/src/utils/argParser.js @@ -0,0 +1,17 @@ +export const parseArgs = (args) => { + const result = {}; + for (let i = 0; i < args.length; i++) { + if (args[i].startsWith("--")) { + const key = args[i].slice(2); + const value = args[i + 1]; + // If the next word doesn't start with --, it's the value + if (value && !value.startsWith("--")) { + result[key] = value; + i++; // Skip the value in the next loop + } else { + result[key] = true; // It's a flag like --save + } + } + } + return result; +}; diff --git a/src/utils/pathResolver.js b/src/utils/pathResolver.js new file mode 100644 index 00000000..ff8a3074 --- /dev/null +++ b/src/utils/pathResolver.js @@ -0,0 +1,7 @@ +import path from "node:path"; + +export const resolvePath = (currentDir, targetPath) => { + if (!targetPath) return currentDir; + // path.resolve handles absolute and relative paths + return path.resolve(currentDir, targetPath); +}; diff --git a/src/workers/logWorker.js b/src/workers/logWorker.js new file mode 100644 index 00000000..87d7c80a --- /dev/null +++ b/src/workers/logWorker.js @@ -0,0 +1,32 @@ +import { parentPort } from "node:worker_threads"; + +// Worker logic to process a log chunk +parentPort.on("message", ({ lines }) => { + const stats = { + total: 0, + levels: { INFO: 0, WARN: 0, ERROR: 0 }, + status: { "2xx": 0, "3xx": 0, "4xx": 0, "5xx": 0 }, + paths: {}, + responseTimeSum: 0, + }; + + lines.forEach((line) => { + if (!line.trim()) return; + const parts = line.split(" "); + if (parts.length < 7) return; + + const [timestamp, level, service, status, responseTime, method, path] = + parts; + + stats.total++; + stats.levels[level] = (stats.levels[level] || 0) + 1; + + const statusClass = `${status[0]}xx`; + stats.status[statusClass] = (stats.status[statusClass] || 0) + 1; + + stats.paths[path] = (stats.paths[path] || 0) + 1; + stats.responseTimeSum += parseInt(responseTime, 10); + }); + + parentPort.postMessage(stats); +}); diff --git a/src/wt/main.js b/src/wt/main.js index d7d21f0c..8431bd01 100644 --- a/src/wt/main.js +++ b/src/wt/main.js @@ -1,11 +1,84 @@ +import { Worker } from "node:worker_threads"; +import { cpus } from "node:os"; +import { readFile, stat } from "node:fs/promises"; +import { resolve } from "node:path"; + +// Write your code here +// Read data.json containing array of numbers +// Split into N chunks (N = CPU cores) +// Create N workers, send one chunk to each +// Collect sorted chunks +// Merge using k-way merge algorithm +// Log final sorted array + +const runWorker = (workerPath, data) => { + return new Promise((resolve, reject) => { + const worker = new Worker(workerPath); + worker.postMessage(data); + + worker.on("message", (result) => resolve(result)); + worker.on("error", (err) => reject(err)); + worker.on("exit", (code) => { + if (code !== 0) + reject(new Error(`Worker stopped with exit code ${code}`)); + }); + }); +}; + +/** + * kWayMerge: Merges multiple sorted arrays into a single sorted array. + */ +const kWayMerge = (arrays) => { + // For simplicity and performance in JS, we flatten and sort, + // but a true k-way merge would use a Min-Priority Queue. + return arrays.flat().sort((a, b) => a - b); +}; + +/** + * main: Reads data, splits it among workers, and merges the results. + */ const main = async () => { - // Write your code here - // Read data.json containing array of numbers - // Split into N chunks (N = CPU cores) - // Create N workers, send one chunk to each - // Collect sorted chunks - // Merge using k-way merge algorithm - // Log final sorted array + const dataPath = resolve(process.cwd(), "data.json"); + const workerPath = resolve(process.cwd(), "src", "wt", "worker.js"); + + try { + // 1. Validation: Ensure data.json exists + await stat(dataPath); + const rawData = await readFile(dataPath, "utf-8"); + const numbers = JSON.parse(rawData); + + // 2. Identify CPU cores (N) + const numCores = cpus().length; + const chunkSize = Math.ceil(numbers.length / numCores); + + // 3. Split data into N chunks + const chunks = []; + for (let i = 0; i < numCores; i++) { + const start = i * chunkSize; + const end = start + chunkSize; + chunks.push(numbers.slice(start, end)); + } + + process.stdout.write( + `Spawning ${numCores} workers to sort ${numbers.length} numbers \n` + ); + + // 4. Create and run workers in parallel + const workerPromises = chunks.map((chunk) => runWorker(workerPath, chunk)); + + // Collect all sorted results + const sortedChunks = await Promise.all(workerPromises); + + // 5. Merge using k-way merge logic + const finalResult = kWayMerge(sortedChunks); + + // 6. Log final result + process.stdout.write("Final sorted array:\n"); + console.log(finalResult); + } catch (error) { + process.stderr.write("Operation failed, Check if data.json exists.\n"); + process.exit(1); + } }; await main(); diff --git a/src/wt/worker.js b/src/wt/worker.js index 15f42fc8..b2a870fd 100644 --- a/src/wt/worker.js +++ b/src/wt/worker.js @@ -1,9 +1,20 @@ -import { parentPort } from 'worker_threads'; +import { parentPort } from "node:worker_threads"; // Receive array from main thread // Sort in ascending order // Send back to main thread -parentPort.on('message', (data) => { - // Write your code here +parentPort.on("message", (data) => { + // Check if data is actually an array + if (!Array.isArray(data)) { + return; + } + + // Sorting in ascending order + // We use (a, b) => a - b because the default sort() + // treats numbers as strings (e.g., 10 comes before 2). + const sortedArray = data.sort((a, b) => a - b); + + // Send the sorted result back to the main thread + parentPort.postMessage(sortedArray); }); diff --git a/src/zip/compressDir.js b/src/zip/compressDir.js index 3a3c5089..20aba34b 100644 --- a/src/zip/compressDir.js +++ b/src/zip/compressDir.js @@ -1,9 +1,75 @@ +import { createBrotliCompress } from "node:zlib"; +import { createWriteStream } from "node:fs"; +import { readdir, stat, readFile, mkdir } from "node:fs/promises"; +import { pipeline } from "node:stream/promises"; +import { resolve, join, relative } from "node:path"; +import { Readable } from "node:stream"; + +// Write your code here +// Read all files from workspace/toCompress/ +// Compress entire directory structure into archive.br +// Save to workspace/compressed/ +// Use Streams API + const compressDir = async () => { - // Write your code here - // Read all files from workspace/toCompress/ - // Compress entire directory structure into archive.br - // Save to workspace/compressed/ - // Use Streams API + const sourceDir = resolve(process.cwd(), "workspace", "toCompress"); + const outputFolder = resolve(process.cwd(), "workspace", "compressed"); + const outputFile = join(outputFolder, "archive.br"); + + try { + // 1. Validation: Ensure source exists + try { + await stat(sourceDir); + } catch { + throw new Error("FS operation failed"); + } + + // 2. Prepare output directory + await mkdir(outputFolder, { recursive: true }); + + // 3. Helper to scan all files + const entries = []; + const scan = async (currentDir) => { + const items = await readdir(currentDir); + for (const item of items) { + const fullPath = join(currentDir, item); + const itemStat = await stat(fullPath); + const relPath = relative(sourceDir, fullPath); + + if (itemStat.isFile()) { + const content = await readFile(fullPath); + entries.push({ + path: relPath, + type: "file", + content: content.toString("base64"), + }); + } else if (itemStat.isDirectory()) { + entries.push({ path: relPath, type: "directory" }); + await scan(fullPath); + } + } + }; + + await scan(sourceDir); + + // 4. Transform the structure into a Stream + const dataString = JSON.stringify({ entries }); + const sourceStream = Readable.from([dataString]); + + // 5. The Compression Pipeline + const compressor = createBrotliCompress(); + const destination = createWriteStream(outputFile); + + // [Image of a data pipeline connecting a source file to a processing unit] + await pipeline(sourceStream, compressor, destination); + + process.stdout.write( + "Directory compressed successfully into archive.br! \n" + ); + } catch (error) { + process.stderr.write("FS operation failed\n"); + process.exit(1); + } }; await compressDir(); diff --git a/src/zip/decompressDir.js b/src/zip/decompressDir.js index d6e770f6..2fe25ed4 100644 --- a/src/zip/decompressDir.js +++ b/src/zip/decompressDir.js @@ -1,8 +1,78 @@ +import { createBrotliDecompress } from "node:zlib"; +import { createReadStream } from "node:fs"; +import { writeFile, mkdir, stat } from "node:fs/promises"; +import { pipeline } from "node:stream/promises"; +import { resolve, join, dirname } from "node:path"; + +// Write your code here +// Read archive.br from workspace/compressed/ +// Decompress and extract to workspace/decompressed/ +// Use Streams API + +/** + * decompressDir: Reads a Brotli compressed archive and restores + * the original directory and file structure. + */ const decompressDir = async () => { - // Write your code here - // Read archive.br from workspace/compressed/ - // Decompress and extract to workspace/decompressed/ - // Use Streams API + const archivePath = resolve( + process.cwd(), + "workspace", + "compressed", + "archive.br" + ); + const outputDir = resolve(process.cwd(), "workspace", "decompressed"); + + try { + // 1. Validation: Ensure archive exists + try { + await stat(archivePath); + } catch { + throw new Error("FS operation failed"); + } + + // 2. Prepare decompression stream + const decompressor = createBrotliDecompress(); + const source = createReadStream(archivePath); + + // 3. Collect the data into a buffer/string + let decompressedData = ""; + + // We can use a special stream to collect the output + const dataCollector = async (stream) => { + for await (const chunk of stream) { + decompressedData += chunk.toString(); + } + }; + + // + // Pipeline: source -> decompressor + await pipeline(source, decompressor, async function* (sourceStream) { + for await (const chunk of sourceStream) { + decompressedData += chunk.toString(); + } + }); + + // 4. Parse the recovered structure + const { entries } = JSON.parse(decompressedData); + + // 5. Recreate files and folders + for (const entry of entries) { + const fullPath = join(outputDir, entry.path); + + if (entry.type === "directory") { + await mkdir(fullPath, { recursive: true }); + } else { + await mkdir(dirname(fullPath), { recursive: true }); + const contentBuffer = Buffer.from(entry.content, "base64"); + await writeFile(fullPath, contentBuffer); + } + } + + process.stdout.write("Archive decompressed successfully! \n"); + } catch (error) { + process.stderr.write("FS operation failed\n"); + process.exit(1); + } }; await decompressDir(); diff --git a/stats.json b/stats.json new file mode 100644 index 00000000..ef9760d5 --- /dev/null +++ b/stats.json @@ -0,0 +1,21 @@ +{ + "total": 1000, + "levels": { + "INFO": 326, + "WARN": 329, + "ERROR": 345 + }, + "status": { + "2xx": 1000, + "3xx": 0, + "4xx": 0, + "5xx": 0 + }, + "topPaths": [ + { + "path": "/api/data", + "count": 1000 + } + ], + "avgResponseTimeMs": 49.85 +} \ No newline at end of file diff --git a/test.csv b/test.csv new file mode 100644 index 00000000..91ec2fba --- /dev/null +++ b/test.csv @@ -0,0 +1 @@ +name,age,city\nAlice,30,Caracas\nBob,25,Maracaibo