[{"content":"I was replacing a 2014 petrol MPV with a used EV. Along the way I collected insurance quotes, checked the vehicle tax bracket, asked for a battery health certificate, got a trade-in offer, and compared a cash price against a dealer\u0026rsquo;s financing plan. Somewhere in the middle of all that I wanted one simple thing: a single number telling me what switching would actually cost per year, compared to just keeping the car I already had.\nI couldn\u0026rsquo;t find a calculator that did that honestly, so I built one.\nThe problem with the calculators that exist Search for a car cost calculator and you\u0026rsquo;ll mostly find two kinds. The first compares fuel costs between two vehicles and stops there — which makes EVs look like an obvious win every time, because it leaves out the line that usually decides the outcome: depreciation. The second kind is a lead-generation funnel dressed up as a calculator, built to get you talking to a dealer or an insurer, not to give you an honest number.\nNeither compares against the option that\u0026rsquo;s actually on the table: keeping the car you have. That car is depreciating too, just slower, and it has running costs of its own. If a candidate vehicle doesn\u0026rsquo;t beat that baseline, it isn\u0026rsquo;t worth switching for, no matter how good the fuel savings look in isolation.\nWhat it does Vehicle cost comparison (source) takes the vehicle you own as the baseline and compares any number of candidates against it. Every candidate gets reduced to one hero number: the annual cost of switching, versus doing nothing. Negative means the candidate is cheaper than keeping what you have; positive means it costs you money to switch, even if it feels nicer to drive.\nUnder that headline number, the full breakdown is there for anyone who wants to argue with it:\nPetrol, diesel, electric and plug-in hybrid on either side of the comparison, with separate cold-weather and normal consumption figures. This turned out to matter more than I expected — a combustion engine on short winter trips can burn 50% more fuel than its rated figure, and an EV loses a lot of range to cabin heating on the same kind of trip. Averaging the two into one annual figure hides a real difference between vehicles. Cash or financed purchase, with actual month-by-month loan amortisation rather than a flat interest guess — including balloon loans, which is what most EV financing offers looked like when I was shopping. The tool warns you if the balloon payment is bigger than the car\u0026rsquo;s estimated value at that point, because that\u0026rsquo;s the scenario where you\u0026rsquo;d owe more than the car is worth. Trade-in value per candidate, not a single shared figure. Different dealers quoted me different numbers for the same car, and a shared trade-in value would have silently corrupted any comparison between them. A residual-value stress test that shifts every candidate\u0026rsquo;s assumed end-of-term value at once, so you can see what a slump in used prices would do to the numbers without re-typing them one by one. An optional baseline, for anyone who owns nothing and just wants to compare candidates against each other. The tool is upfront that this is a weaker framing than a real delta — an absolute total isn\u0026rsquo;t the same claim as \u0026ldquo;cheaper than what you have.\u0026rdquo; Every input is a plain, editable field — nothing is hidden, and nothing is estimated behind the scenes. There\u0026rsquo;s no valuation data baked in; you type in a purchase price and an expected end value, the same numbers you\u0026rsquo;d take to a spreadsheet.\nIt\u0026rsquo;s also a single static HTML file with no backend, no account, and no tracking. Your scenario lives in your browser\u0026rsquo;s local storage, and if you want to share it, a Copy link button compresses the whole thing into the URL fragment — the part after #, which browsers never send to a server — so a five-vehicle comparison turns into a link you can paste into a message.\nWhat it deliberately leaves out The spec I wrote before building this has a whole section of non-goals, and I think the reasoning is worth stating plainly rather than leaving people to assume an oversight:\nNo valuation lookups. The tool doesn\u0026rsquo;t know what your car, or the one you\u0026rsquo;re considering, is actually worth. Getting that wrong silently would be worse than asking you to type in a number you got from a real quote. No tax or insurance API. Both are quoted per person and per vehicle in ways a lookup can\u0026rsquo;t capture accurately. You enter the number your insurer or tax authority actually gave you. The baseline assumes your current car survives the whole comparison period. If it doesn\u0026rsquo;t — if it fails in year three — you\u0026rsquo;d buy something sooner than planned, and the model doesn\u0026rsquo;t try to predict that. It\u0026rsquo;s a known bias in favour of keeping what you have, and I\u0026rsquo;d rather say so than pretend the model accounts for it. Metric units only, for now. Distance in km, consumption per 100 km or 100 kWh. An imperial toggle is on the list but didn\u0026rsquo;t make the first cut. No CSV export, print stylesheet, or country presets yet, and no way to import a candidate straight from a classifieds listing — that last one turns out to need either a proxy, a browser extension, or you pasting in the page content, since a static site can\u0026rsquo;t fetch another site\u0026rsquo;s page directly. All of that is written up in the spec as later phases, not forgotten. One external request. The page loads Google Fonts. Everything else — the calculation, your data, the shared link — never leaves your device. None of this is because the missing pieces are hard; it\u0026rsquo;s because a plausible-but-wrong number is worse than an honest gap. The tool would rather ask you for a real figure than guess one.\nTrying it It\u0026rsquo;s live at slmnn.fi/vehicle-tco, the source and the full design spec are on GitHub, and it\u0026rsquo;s MIT licensed if you want to fork it for your own country\u0026rsquo;s tax and insurance quirks. If you end up adding a preset for fuel or electricity prices where you live, that\u0026rsquo;s exactly the kind of contribution the project is set up for.\n","permalink":"https://artosalminen.github.io/posts/how-i-built-a-vehicle-cost-comparison-tool/","summary":"\u003cp\u003eI was replacing a 2014 petrol MPV with a used EV. Along the way I collected insurance quotes, checked the vehicle tax bracket, asked for a battery health certificate, got a trade-in offer, and compared a cash price against a dealer\u0026rsquo;s financing plan. Somewhere in the middle of all that I wanted one simple thing: a single number telling me what switching would actually cost per year, compared to just keeping the car I already had.\u003c/p\u003e","title":"How I Built a Vehicle Cost Comparison Tool That Doesn't Ignore Depreciation"},{"content":"If you\u0026rsquo;re running your Vitest suite on a high thread count machine—like an 8-core or 16-core development machine—you may encounter a frustrating error that looks like this:\nEMFILE: too many open files This issue can be cryptic at first, especially when your system doesn\u0026rsquo;t seem anywhere near running out of resources. But the root cause might not be in your system limits—it could be in your import statements.\nThe Unexpected Culprit: Named Imports from MUI In my case, after hours of digging, I discovered that the error was caused by using named imports from Material UI (MUI) components and icons. These named imports, when used across many test files, were triggering Vitest to open an excessive number of files behind the scenes.\nWhy? Because a named import from a large package like @mui/icons-material can cause the entire module to be parsed and analyzed—even if you only needed one icon. This leads to an explosion of open file handles, especially when Vitest is running in parallel across many threads.\nProblematic (Named) Import – ❌ // This opens the entire icon module behind the scenes import { Delete } from \u0026#39;@mui/icons-material\u0026#39;; Correct (Default) Import – ✅ // This only opens the specific icon file import Delete from \u0026#39;@mui/icons-material/Delete\u0026#39;; The same applies for other large packages, such as components from @mui/material.\nMore Invalid vs Valid Examples // ❌ Named import (bad) import { Button } from \u0026#39;@mui/material\u0026#39;; // ✅ Default import (good) import Button from \u0026#39;@mui/material/Button\u0026#39;; Why This Happens When Vitest runs your tests, it analyzes and processes all the imported files. With named imports, tools like Vite (and by extension Vitest) may resolve the full package entry point and end up opening hundreds or even thousands of files—especially for large packages like MUI. Multiply that across many test files and parallel threads, and you can quickly hit your OS file descriptor limit.\nThe Fix Replace all named imports from MUI packages with default imports from the specific component or icon path. Optionally, increase your OS file descriptor limit (ulimit), though this only postpones the underlying problem. Restart your test runner and watch the errors disappear. ","permalink":"https://artosalminen.github.io/posts/how-to-resolve-vitest-mui-icons-emfile-too-many-open-files-error/","summary":"\u003cp\u003eIf you\u0026rsquo;re running your Vitest suite on a high thread count machine—like an 8-core or 16-core development machine—you may encounter a frustrating error that looks like this:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eEMFILE: too many open files\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThis issue can be cryptic at first, especially when your system doesn\u0026rsquo;t seem anywhere near running out of resources. But the root cause might not be in your system limits—it could be in your import statements.\u003c/p\u003e\n\u003ch2 id=\"the-unexpected-culprit-named-imports-from-mui\"\u003eThe Unexpected Culprit: Named Imports from MUI\u003c/h2\u003e\n\u003cp\u003eIn my case, after hours of digging, I discovered that the error was caused by using \u003cstrong\u003enamed imports\u003c/strong\u003e from Material UI (MUI) components and icons. These named imports, when used across many test files, were triggering Vitest to open an excessive number of files behind the scenes.\u003c/p\u003e","title":"How to Resolve Vitest `EMFILE: too many open files` Errors on a High Thread Count Machine"},{"content":"Streaming audio locally can be useful for broadcasting live audio to multiple devices or applications within the same network. With FFmpeg, this setup becomes simple, allowing you to stream audio directly from your microphone to any player that supports UDP streaming, like VLC. This guide explains how to set up FFmpeg on Windows, configure a low-latency stream, and tune it for optimal performance.\nStep 1: Install FFmpeg on Windows To use FFmpeg on Windows, you’ll need to download and install it. Follow these steps:\nDownload FFmpeg: Go to the official FFmpeg website and download the \u0026ldquo;FFmpeg Release\u0026rdquo; for Windows. This will typically download as a .zip file. Extract the Files: Once downloaded, unzip the FFmpeg folder to a location of your choice (e.g., C:\\ffmpeg). Add FFmpeg to the System Path: Open the Start Menu and search for “Environment Variables.” Select Edit the system environment variables. In the System Properties window, click Environment Variables. Under System Variables, find the Path variable, select it, and click Edit. Add the path to the bin folder of FFmpeg (e.g., C:\\ffmpeg\\bin). Click OK to save and close each window. Verify Installation: Open Command Prompt and type ffmpeg -version. If installed correctly, FFmpeg should display its version and configuration information. Step 2: Streaming Audio from Microphone Locally Now that FFmpeg is installed, you can start streaming audio from your microphone. Here’s a command to stream audio with low latency, optimized for UDP on the local network.\nffmpeg -f dshow -i audio=\u0026#34;Microphone (AUDIO 2.0)\u0026#34; -acodec libmp3lame -b:a 128k -bufsize 32k -fflags nobuffer -flags low_delay -flush_packets 1 -max_delay 0 -f mp3 udp://127.0.0.1:1234 Command Breakdown -f dshow: Specifies DirectShow as the input format on Windows, which allows FFmpeg to access your microphone. -i audio=\u0026quot;Microphone (AUDIO 2.0)\u0026quot;: Selects the microphone device. Replace \u0026quot;Microphone (AUDIO 2.0)\u0026quot; with the exact name of your microphone as recognized by FFmpeg. -acodec libmp3lame: Sets the MP3 codec for encoding audio, providing good compression with manageable latency. -b:a 128k: Sets the audio bitrate to 128 kbps, balancing quality and bandwidth usage. -bufsize 32k: Sets a small buffer size, reducing latency by speeding up the buffer fill time. -fflags nobuffer: Reduces buffering on the input side to decrease overall latency. -flags low_delay: Enables low-latency mode for faster processing. -flush_packets 1: Flushes packets immediately after encoding, reducing delay. -max_delay 0: Sets the maximum delay to zero, minimizing additional buffering. -f mp3: Specifies the MP3 format for the output stream. udp://127.0.0.1:1234: Sends the audio stream to UDP port 1234 on the local machine. Change 127.0.0.1 to your computer’s IP if you want other devices on the network to access the stream. Step 3: Accessing the Stream in VLC Media Player To listen to the streamed audio, use a media player that supports UDP streaming, such as VLC:\nOpen VLC: Launch VLC on the same computer or any other device within the local network. Open Network Stream: In VLC, go to Media \u0026gt; Open Network Stream. Enter the following URL: udp://@:1234. Adjust Buffering (if necessary): VLC might buffer the stream by default, which can add a delay. To reduce it, go to Tools \u0026gt; Preferences \u0026gt; Input/Codecs. Set the Network caching value to a low number (e.g., 300 ms). Fine-Tuning for Optimal Performance Depending on your network and system performance, you may want to experiment with these additional tips:\nAdjust Bitrate: Lowering the bitrate with -b:a 96k or -b:a 64k will use less bandwidth, reducing the load on the network, which can help with latency. Experiment with Audio Format: While MP3 is a good balance between quality and latency, other formats like AAC may offer lower latency depending on your setup. Try replacing libmp3lame with aac for comparison. Use a Different Port: If port 1234 is already in use, you can replace it with an open port number. Wrapping Up By following these steps, you’ve set up a low-latency audio stream on your local network using FFmpeg. This command is a great starting point for live audio applications such as broadcasting, intercom setups, or audio monitoring in a local environment. For those who need higher performance, additional tuning options with FFmpeg and VLC settings can help achieve an ideal balance between quality and latency.\nHappy streaming!\n","permalink":"https://artosalminen.github.io/posts/how-to-stream-audio-in-local-network-with-ffmpeg/","summary":"\u003cp\u003eStreaming audio locally can be useful for broadcasting live audio to multiple devices or applications within the same network. With FFmpeg, this setup becomes simple, allowing you to stream audio directly from your microphone to any player that supports UDP streaming, like VLC. This guide explains how to set up FFmpeg on Windows, configure a low-latency stream, and tune it for optimal performance.\u003c/p\u003e\n\u003ch2 id=\"step-1-install-ffmpeg-on-windows\"\u003eStep 1: Install FFmpeg on Windows\u003c/h2\u003e\n\u003cp\u003eTo use FFmpeg on Windows, you’ll need to download and install it. Follow these steps:\u003c/p\u003e","title":"How to stream audio in local network with FFMpeg?"},{"content":"Updating nested array subdocuments based on a field in the main document is a common task when working with MongoDB and Mongoose. Here\u0026rsquo;s a simple guide and example to help you accomplish this.\nPlan Use Model.updateMany() or Model.findOneAndUpdate() to update multiple or a single document. Use an aggregation pipeline ([{}]) as the second argument to reference document fields. Use the $set stage within the pipeline to update the nested array. Use the $$ROOT variable to reference fields from the main document. Example Assume you have a User model where each user has an array of posts, and you want to set each post\u0026rsquo;s authorName field based on the user\u0026rsquo;s name field.\nHere\u0026rsquo;s how you can do it:\nconst mongoose = require(\u0026#39;mongoose\u0026#39;); const { Schema } = mongoose; const postSchema = new Schema({ title: String, content: String, authorName: String, // This is what we want to update }); const userSchema = new Schema({ name: String, posts: [postSchema], }); const User = mongoose.model(\u0026#39;User\u0026#39;, userSchema); async function updatePostAuthorNames() { await User.updateMany( {}, // Update condition, {} means all documents [ { $set: { posts: { $map: { input: \u0026#34;$posts\u0026#34;, as: \u0026#34;post\u0026#34;, in: { $mergeObjects: [ \u0026#34;$$post\u0026#34;, { authorName: \u0026#34;$name\u0026#34; } // Set authorName in each post to the user\u0026#39;s name ] } } } } } ] ); } updatePostAuthorNames().then(() =\u0026gt; console.log(\u0026#39;Update complete.\u0026#39;)); Explanation Define Schemas: Define the postSchema and userSchema. Update Logic: Use updateMany() to update all user documents. Use an aggregation pipeline to modify the documents. The $set stage updates the posts array. The $map operator iterates over the posts array. The $mergeObjects operator merges the original post object ($$post) with a new object containing the updated authorName set to the user\u0026rsquo;s name. Running the Update To run the update function, simply call it:\nupdatePostAuthorNames().then(() =\u0026gt; console.log(\u0026#39;Update complete.\u0026#39;)); This script updates the authorName for all posts based on their respective user\u0026rsquo;s name.\nConclusion Updating nested array subdocuments using a main document field value in MongoDB with Mongoose is straightforward with the right approach. Using aggregation pipelines and Mongoose\u0026rsquo;s powerful querying capabilities, you can efficiently perform these updates.\n","permalink":"https://artosalminen.github.io/posts/how-to-update-nested-array-with-mongoose/","summary":"\u003cp\u003eUpdating nested array subdocuments based on a field in the main document is a common task when working with MongoDB and Mongoose. Here\u0026rsquo;s a simple guide and example to help you accomplish this.\u003c/p\u003e\n\u003ch3 id=\"plan\"\u003ePlan\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003eUse \u003ccode\u003eModel.updateMany()\u003c/code\u003e or \u003ccode\u003eModel.findOneAndUpdate()\u003c/code\u003e to update multiple or a single document.\u003c/li\u003e\n\u003cli\u003eUse an aggregation pipeline (\u003ccode\u003e[{}]\u003c/code\u003e) as the second argument to reference document fields.\u003c/li\u003e\n\u003cli\u003eUse the \u003ccode\u003e$set\u003c/code\u003e stage within the pipeline to update the nested array.\u003c/li\u003e\n\u003cli\u003eUse the \u003ccode\u003e$$ROOT\u003c/code\u003e variable to reference fields from the main document.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"example\"\u003eExample\u003c/h3\u003e\n\u003cp\u003eAssume you have a \u003ccode\u003eUser\u003c/code\u003e model where each user has an array of \u003ccode\u003eposts\u003c/code\u003e, and you want to set each post\u0026rsquo;s \u003ccode\u003eauthorName\u003c/code\u003e field based on the user\u0026rsquo;s \u003ccode\u003ename\u003c/code\u003e field.\u003c/p\u003e","title":"How to Update Nested Array Subdocuments Using Main Document Field with Mongoose"},{"content":"How to Download Streaming Video Using ffmpeg Streaming video has become ubiquitous, but sometimes you might want to save a portion of a video for offline viewing or archival purposes. One powerful tool for this task is ffmpeg, a versatile command-line program for handling multimedia data. In this guide, we\u0026rsquo;ll walk you through the process of installing ffmpeg and using it to download streaming video.\nInstalling ffmpeg Before you can use ffmpeg, you need to install it on your system. One of the simplest ways to install ffmpeg on Windows is via Chocolatey, a package manager for Windows. Follow these steps to install ffmpeg:\nInstall Chocolatey: If you haven\u0026rsquo;t installed Chocolatey yet, open a command prompt with administrative privileges and run the following command:\n@\u0026#34;%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\u0026#34; -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command \u0026#34;iex ((New-Object System.Net.WebClient).DownloadString(\u0026#39;https://chocolatey.org/install.ps1\u0026#39;))\u0026#34; \u0026amp;\u0026amp; SET \u0026#34;PATH=%PATH%;%ALLUSERSPROFILE%\\chocolatey\\bin\u0026#34; Install ffmpeg: Once Chocolatey is installed, you can install ffmpeg by running the following command in the command prompt:\nchoco install ffmpeg-full This command installs the full version of ffmpeg, which includes all the libraries and codecs needed for a wide range of video and audio processing tasks.\nUsing ffmpeg to Download Streaming Video Once ffmpeg is installed, you can use it to download and save a portion of a streaming video. The basic command to achieve this is:\nffmpeg -ss [start timestamp] -i [video path or url] -t [duration] [outputname.mp4] Breaking Down the Command -ss [start timestamp]: This option specifies the starting point of the video clip you want to download. The timestamp should be in the format hh:mm:ss. For example, 00:01:30 starts the clip at 1 minute and 30 seconds.\n-i [video path or url]: This option specifies the input file or URL of the streaming video. Replace [video path or url] with the actual path or URL of the video you want to download.\n-t [duration]: This option specifies the duration of the clip you want to download, starting from the timestamp specified by -ss. The duration should be in the format hh:mm:ss. For example, 00:00:30 will download a 30-second clip.\n[outputname.mp4]: This is the name of the output file where the downloaded clip will be saved. Replace outputname.mp4 with your desired file name.\nExample Command Let\u0026rsquo;s say you want to download a 1-minute clip from a video starting at 2 minutes and 30 seconds. If the URL of the video is http://example.com/video.mp4, you would use the following command:\nffmpeg -ss 00:02:30 -i http://example.com/video.mp4 -t 00:01:00 myclip.mp4 This command tells ffmpeg to start 2 minutes and 30 seconds into the video, download a clip that lasts for 1 minute, and save it as myclip.mp4.\nTips and Tricks Adjusting Quality: You can adjust the quality of the output video by adding options like -b:v for video bitrate and -b:a for audio bitrate. For example, -b:v 1000k sets the video bitrate to 1000 kbps.\nHandling Errors: If you encounter errors, make sure the video URL is accessible and that ffmpeg supports the streaming format of the video. Some streaming services use encryption or DRM that ffmpeg cannot bypass.\nFurther Learning: ffmpeg is an incredibly powerful tool with many options and features. The official ffmpeg documentation is a great resource to learn more about its capabilities.\nBy following these steps, you can easily download and save streaming video clips using ffmpeg. Whether you\u0026rsquo;re archiving important content or just want to watch videos offline, ffmpeg provides a flexible and powerful solution.\n","permalink":"https://artosalminen.github.io/posts/how-to-download-streaming-video-with-ffmpeg/","summary":"\u003ch1 id=\"how-to-download-streaming-video-using-ffmpeg\"\u003eHow to Download Streaming Video Using ffmpeg\u003c/h1\u003e\n\u003cp\u003eStreaming video has become ubiquitous, but sometimes you might want to save a portion of a video for offline viewing or archival purposes. One powerful tool for this task is \u003ccode\u003effmpeg\u003c/code\u003e, a versatile command-line program for handling multimedia data. In this guide, we\u0026rsquo;ll walk you through the process of installing \u003ccode\u003effmpeg\u003c/code\u003e and using it to download streaming video.\u003c/p\u003e\n\u003ch2 id=\"installing-ffmpeg\"\u003eInstalling ffmpeg\u003c/h2\u003e\n\u003cp\u003eBefore you can use \u003ccode\u003effmpeg\u003c/code\u003e, you need to install it on your system. One of the simplest ways to install \u003ccode\u003effmpeg\u003c/code\u003e on Windows is via Chocolatey, a package manager for Windows. Follow these steps to install \u003ccode\u003effmpeg\u003c/code\u003e:\u003c/p\u003e","title":"How to Download Streaming Video Using ffmpeg"},{"content":"In any API development project, ensuring that sensitive or unnecessary data is not exposed in the responses is crucial for security and efficiency. In NestJS, interceptors provide a powerful way to manipulate the flow of data. In this blog post, we\u0026rsquo;ll explore how to use an interceptor to sanitize API responses by removing specific properties. We\u0026rsquo;ll also look at how to write tests to ensure the interceptor behaves as expected.\nInterceptors can be used to manipulate API responses in other ways as well. For instance, for creating pagination headers or similar purposes.\nBackground Before diving into the implementation, let\u0026rsquo;s briefly understand the purpose of the interceptor and the scenario it addresses. The ResponsePayloadSanitizationInterceptor is designed to remove specific properties (e.g. 'my_super_secret_secret') from the API responses. These properties are often used internally by databases and might not be relevant or safe to expose to the clients consuming the API.\nImplementation The interceptor class ResponsePayloadSanitizationInterceptor implements the NestInterceptor interface. It intercepts the response data and uses a recursive approach to remove the specified properties from objects and arrays. The interceptor is applied globally to the application using app.useGlobalInterceptors(new ResponsePayloadSanitizationInterceptor()).\nimport { Injectable, NestInterceptor, ExecutionContext, CallHandler } from \u0026#39;@nestjs/common\u0026#39; import { Observable } from \u0026#39;rxjs\u0026#39; import { map } from \u0026#39;rxjs/operators\u0026#39; @Injectable() export class ResponsePayloadSanitizationInterceptor\u0026lt;T\u0026gt; implements NestInterceptor\u0026lt;T\u0026gt; { readonly omittedProperties = [\u0026#39;my_super_secret_secret\u0026#39;] intercept(context: ExecutionContext, next: CallHandler): Observable\u0026lt;T\u0026gt; { return next.handle().pipe(map((data) =\u0026gt; this.sanitizeProperties(data))) } private sanitizeProperties(data: any): any { if (Array.isArray(data)) { return data.map((item) =\u0026gt; this.sanitizeProperties(item)) } if (typeof data === \u0026#39;object\u0026#39; \u0026amp;\u0026amp; data !== null) { const sanitizedData = {} for (const key in data) { if (!this.omittedProperties.includes(key)) { sanitizedData[key] = this.sanitizeProperties(data[key]) } } return sanitizedData } return data } } Writing Tests Writing tests is a crucial step to ensure that the interceptor behaves as expected. In this case, the test suite focuses on the TestController and verifies that the interceptor removes the specified property ('my_super_secret_secret') from the API response.\nimport { Controller, Get, HttpStatus, INestApplication } from \u0026#39;@nestjs/common\u0026#39; import { Test } from \u0026#39;@nestjs/testing\u0026#39; import { Public } from \u0026#39;nest-keycloak-connect\u0026#39; import * as request from \u0026#39;supertest\u0026#39; import { ResponsePayloadSanitizationInterceptor } from \u0026#39;./response-payload-sanitization.interceptor\u0026#39; import { AppModule } from \u0026#39;../../app.module\u0026#39; @Public() @Controller() class TestController { // eslint-disable-next-line class-methods-use-this @Get(\u0026#39;/api/test\u0026#39;) test() { return { results: [ { my_super_secret_secret: \u0026#39;1\u0026#39;, value: \u0026#39;hey\u0026#39;, foo: { my_super_secret_secret: \u0026#39;2\u0026#39;, value: \u0026#39;hoy\u0026#39;, }, }, ], } } } describe(\u0026#39;ResponsePayloadSanitizationInterceptor\u0026#39;, () =\u0026gt; { let app: INestApplication beforeEach(async () =\u0026gt; { app = ( await Test.createTestingModule({ imports: [AppModule], controllers: [TestController], }).compile() ).createNestApplication() app.useGlobalInterceptors(new ResponsePayloadSanitizationInterceptor()) }) it(\u0026#39;should remove all secret properties from response\u0026#39;, async () =\u0026gt; { await app.init() const response = await request(app.getHttpServer()).get(\u0026#39;/api/test\u0026#39;) expect(response.status).toBe(HttpStatus.OK) expect(JSON.stringify(response.body)).toBe( JSON.stringify({ results: [ { value: \u0026#39;hey\u0026#39;, foo: { value: \u0026#39;hoy\u0026#39;, }, }, ], }) ) }) afterEach(async () =\u0026gt; { await app.close() }) }) The test case should remove all secret properties from response initializes the NestJS application, sends a request to the /api/test endpoint, and checks if the interceptor has successfully removed the specified properties from the response. The supertest library is used to make HTTP requests and assert the expected behavior.\nConclusion Using interceptors in NestJS provides a clean and reusable way to modify the behavior of your API. The ResponsePayloadSanitizationInterceptor showcased here is a practical example of how interceptors can be used to sanitize API responses by removing specific properties. Writing tests ensures that the interceptor functions correctly and provides a safety net for future changes.\nBy incorporating this interceptor into your NestJS application, you can enhance the security and cleanliness of your API responses, making them more suitable for consumption by client applications.\n","permalink":"https://artosalminen.github.io/posts/how-to-manipulate-nestjs-response/","summary":"\u003cp\u003eIn any API development project, ensuring that sensitive or unnecessary data is not exposed in the responses is crucial for security and efficiency. In NestJS, interceptors provide a powerful way to manipulate the flow of data. In this blog post, we\u0026rsquo;ll explore how to use an interceptor to sanitize API responses by removing specific properties. We\u0026rsquo;ll also look at how to write tests to ensure the interceptor behaves as expected.\u003c/p\u003e","title":"How to sanitize NestJS API responses with Interceptor"},{"content":"For developers working on .NET Core and other development servers, encountering port conflicts can be a common issue. Recently, I came accross with the default port (5000) used for local development server and the Intel(R) Graphics Command Center server. In this blog post, I\u0026rsquo;ll introduce a workaround that involves editing the Windows registry to resolve the conflict and ensure smooth development processes.\nUnderstanding the Issue:\nThe Intel(R) Graphics Command Center server, specifically the OneApp.IGCC.WinService.exe executable, by default, utilizes port 5000. However, this port is commonly used for development servers in the .NET Core ecosystem and other software development environments. When both the Intel(R) Graphics Command Center server and a development server attempt to bind to port 5000 simultaneously, a conflict arises.\nThe Workaround:\nTo address this issue, we can implement a workaround by editing the Windows registry and appending the \u0026ndash;urls parameter to the startup command of the Intel(R) Graphics Command Center service executable. This involves specifying an alternative port, such as 50000, to avoid clashes with the default port used for development servers.\nHere are the step-by-step instructions:\nAccessing the Windows Registry:\nPress Win + R to open the Run dialog. Type regedit and press Enter to open the Registry Editor. Navigate to HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\OneApp.IGCC.WinService. Editing the Registry Entry:\nLocate the ImagePath entry on the right-hand side, which holds the path to the OneApp.IGCC.WinService.exe executable. Append --urls http://127.0.0.1:50000 to the existing path. Ensure that there is a space before the double dash (\u0026ndash;). Example:\n\u0026#34;C:\\Path\\To\\OneApp.IGCC.WinService.exe\u0026#34; --urls http://127.0.0.1:50000 Save the changes.\nRestart the Service:\nRestart the Intel(R) Graphics Command Center service to apply the new configuration. Open the Run dialog again (Win + R), type services.msc, and press Enter. Locate the Intel(R) Graphics Command Center service, right-click, and choose Restart. By following these steps, you have successfully configured the Intel(R) Graphics Command Center server to use an alternative port (50000), avoiding conflicts with the default port used for .NET Core and other development servers.\nConclusion:\nPort conflicts can be a hurdle for developers working on various projects simultaneously. This workaround provides a solution for users encountering conflicts between the Intel(R) Graphics Command Center server and development servers using the default port 5000. By making a simple registry edit, you can ensure a smoother development experience without compromising the functionality of either service.\n","permalink":"https://artosalminen.github.io/posts/how-to-resolve-intel-control-center-port-conflict/","summary":"\u003cp\u003eFor developers working on .NET Core and other development servers, encountering port conflicts can be a common issue. Recently, I came accross with the default port (5000) used for local development server and the Intel(R) Graphics Command Center server. In this blog post, I\u0026rsquo;ll introduce a workaround that involves editing the Windows registry to resolve the conflict and ensure smooth development processes.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eUnderstanding the Issue:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe Intel(R) Graphics Command Center server, specifically the OneApp.IGCC.WinService.exe executable, by default, utilizes port 5000. However, this port is commonly used for development servers in the .NET Core ecosystem and other software development environments. When both the Intel(R) Graphics Command Center server and a development server attempt to bind to port 5000 simultaneously, a conflict arises.\u003c/p\u003e","title":"How to resolve Intel Graphics Command Center port conflict"},{"content":"In this post, I will show you how to create a custom React hook that provides locale-specific date formatting functions. This hook can be useful if you want to display dates and times in different languages and formats depending on the user\u0026rsquo;s preferences.\nWhat is a custom React hook? A custom React hook is a function that starts with the word use and may call other hooks. Custom hooks let you reuse stateful logic between components without duplicating code or introducing complex patterns. You can learn more about custom hooks from the official React documentation (1) or this tutorial (2).\nWhat is Intl.DateTimeFormat? Intl.DateTimeFormat is a JavaScript object that enables language-sensitive date and time formatting. It takes a locale (a string that identifies a language and a region) and an options object (that specifies how to format the date and time) as arguments and returns a formatter object that has a format method. You can use this method to convert a Date object into a string that follows the locale and options you provided. You can also use other methods such as formatToParts and formatRange to get more control over the output. You can learn more about Intl.DateTimeFormat from the MDN web docs (3) or this Stack Overflow answer (4).\nHow to create the custom hook? The custom hook we want to create is called useLocaleDateFormat. It takes no arguments and returns an object containing the default locale and three formatting functions: formatDate, formatTime, and formatDateTime. These functions take a Date object as an argument and return a string that represents the date, the time, or the date and time in the default locale.\nHere is the code of the custom hook:\nimport React from \u0026#39;react\u0026#39;; interface UseLocaleDateFormat { defaultLocale: string; formatDate: (value: Date) =\u0026gt; string; formatTime: (value: Date) =\u0026gt; string; formatDateTime: (value: Date) =\u0026gt; string; } /** * Custom hook that provides locale-specific date formatting functions. * @returns An object containing the default locale and formatting functions for date, time, and date-time. */ export function useLocaleDateFormat(): UseLocaleDateFormat { let defaultLocale = (typeof navigator !== \u0026#39;undefined\u0026#39; \u0026amp;\u0026amp; navigator.language) || \u0026#39;en-US\u0026#39;; try { // @ts-ignore Intl.DateTimeFormat.supportedLocalesOf([defaultLocale]); } catch (_err) { defaultLocale = \u0026#39;en-US\u0026#39;; } const value = React.useMemo(() =\u0026gt; { return { defaultLocale, formatDate: (value: Date) =\u0026gt; value.toLocaleDateString(defaultLocale), formatTime: (value: Date) =\u0026gt; value.toLocaleTimeString(defaultLocale, { hour: \u0026#39;2-digit\u0026#39;, minute: \u0026#39;2-digit\u0026#39; }), formatDateTime: (value: Date) =\u0026gt; `${value.toLocaleDateString(defaultLocale)} ${value.toLocaleTimeString(defaultLocale, { hour: \u0026#39;2-digit\u0026#39;, minute: \u0026#39;2-digit\u0026#39;, })}`, }; }, [defaultLocale]); return value; } Let\u0026rsquo;s break down the code and explain each part.\nDefine the interface The first thing we do is to define an interface that describes the shape of the object that the hook returns. This is optional, but it helps us to write type-safe code and get better editor support. The interface has four properties: defaultLocale, formatDate, formatTime, and formatDateTime. The first one is a string that represents the default locale, and the rest are functions that take a Date object and return a string.\nGet the default locale The next thing we do is to get the default locale from the navigator.language property, which returns the language of the browser. This property may not be available in some environments, such as server-side rendering, so we use a fallback value of 'en-US' in case it is undefined. We also use a try-catch block to check if the locale is supported by Intl.DateTimeFormat using the supportedLocalesOf method. If the locale is not supported, we also use the fallback value of 'en-US'.\nUse React.useMemo The next thing we do is to use the React.useMemo hook to memoize the value of the object that contains the default locale and the formatting functions. This hook takes a function that returns the value and an array of dependencies as arguments and returns the memoized value. The memoized value is only recomputed when one of the dependencies changes, which in this case is the default locale. This way, we avoid creating a new object and new functions on every render, which can improve performance and avoid unnecessary re-renders of the components that use the hook.\nCreate the formatting functions The last thing we do is to create the formatting functions using the toLocaleDateString, toLocaleTimeString, and template literals. These functions use the default locale and some options to format the date and time according to the user\u0026rsquo;s preferences. For example, the formatDate function uses the toLocaleDateString method, which returns a string with a language-sensitive representation of the date portion of the Date object. The formatTime function uses the toLocaleTimeString method, which returns a string with a language-sensitive representation of the time portion of the Date object. The formatDateTime function uses both methods and a template literal to combine the date and time strings into one.\nHow to use the custom hook? To use the custom hook, we simply import it and call it inside a functional component. We can then use the returned object to access the default locale and the formatting functions. For example, we can create a component that displays the current date and time in the default locale:\nimport React from \u0026#39;react\u0026#39;; import { useLocaleDateFormat } from \u0026#39;./useLocaleDateFormat\u0026#39;; export function CurrentDateTime() { const { defaultLocale, formatDateTime } = useLocaleDateFormat(); const [now, setNow] = React.useState(new Date()); React.useEffect(() =\u0026gt; { const timer = setInterval(() =\u0026gt; { setNow(new Date()); }, 1000); return () =\u0026gt; { clearInterval(timer); }; }, []); return ( \u0026lt;div\u0026gt; \u0026lt;p\u0026gt;The current date and time in {defaultLocale} is:\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;{formatDateTime(now)}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; ); } This component uses the useLocaleDateFormat hook to get the default locale and the formatDateTime function. It also uses the useState and useEffect hooks to create a state variable for the current date and time and update it every second using a timer. It then renders a paragraph that shows the current date and time in the default locale.\nTests And here are a couple of tests for the useLocaleDateFormat hook.\nimport { renderHook } from \u0026#39;@testing-library/react\u0026#39;; import { useLocaleDateFormat } from \u0026#39;./useLocaleDateFormat\u0026#39;; describe(\u0026#39;useLocaleDateFormat\u0026#39;, () =\u0026gt; { const locale = vitest.spyOn(navigator, \u0026#39;language\u0026#39;, \u0026#39;get\u0026#39;); it(\u0026#39;should format date, time, and datetime correctly in en-US locale\u0026#39;, () =\u0026gt; { locale.mockReturnValue(\u0026#39;en-US\u0026#39;); const { result } = renderHook(() =\u0026gt; useLocaleDateFormat()); expect(result.current.defaultLocale).toEqual(\u0026#39;en-US\u0026#39;); const date = new Date(2022, 0, 15, 12, 30, 0); const formattedDate = result.current.formatDate(date); expect(formattedDate).toEqual(\u0026#39;1/15/2022\u0026#39;); const formattedTime = result.current.formatTime(date); expect(formattedTime).toEqual(\u0026#39;12:30 PM\u0026#39;); const formattedDateTime = result.current.formatDateTime(date); expect(formattedDateTime).toEqual(\u0026#39;1/15/2022 12:30 PM\u0026#39;); }); it(\u0026#39;should format date, time, and datetime correctly in fi-FI locale\u0026#39;, () =\u0026gt; { locale.mockReturnValue(\u0026#39;fi-FI\u0026#39;); const { result } = renderHook(() =\u0026gt; useLocaleDateFormat()); expect(result.current.defaultLocale).toEqual(\u0026#39;fi-FI\u0026#39;); const date = new Date(2022, 0, 15, 12, 30, 0); const formattedDate = result.current.formatDate(date); expect(formattedDate).toEqual(\u0026#39;15.1.2022\u0026#39;); const formattedTime = result.current.formatTime(date); expect(formattedTime).toEqual(\u0026#39;12.30\u0026#39;); const formattedDateTime = result.current.formatDateTime(date); expect(formattedDateTime).toEqual(\u0026#39;15.1.2022 12.30\u0026#39;); }); }); Conclusion I hope you found this post helpful and learned something new. If you have any questions or feedback, please let me know in the comments. Thank you for reading! 😊.\nHere is some further reading for you: Reusing Logic with Custom Hooks – React - GitHub Pages. https://react.dev/learn/reusing-logic-with-custom-hooks. How to create your own custom React Hooks - LogRocket Blog. https://blog.logrocket.com/create-your-own-custom-react-hooks/. Intl.DateTimeFormat - JavaScript | MDN - MDN Web Docs. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat. How to format a JavaScript date object using Intl.DateTimeFormat. https://stackoverflow.com/questions/60672126/how-to-format-a-javascript-date-object-using-intl-datetimeformat. Intl.DateTimeFormat() constructor - JavaScript | MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat. Custom Hooks | Hands on React. https://handsonreact.com/docs/custom-hooks. useHooks – The React Hooks Library. https://usehooks.com/. React Custom Hook tutorial with example - BezKoder. https://www.bezkoder.com/react-custom-hook/. en.wikipedia.org. https://en.wikipedia.org/wiki/React_(software). ","permalink":"https://artosalminen.github.io/posts/how-to-implement-hook-to-format-dates-in-react/","summary":"\u003cp\u003eIn this post, I will show you how to create a custom React hook that provides locale-specific date formatting functions. This hook can be useful if you want to display dates and times in different languages and formats depending on the user\u0026rsquo;s preferences.\u003c/p\u003e\n\u003ch2 id=\"what-is-a-custom-react-hook\"\u003eWhat is a custom React hook?\u003c/h2\u003e\n\u003cp\u003eA custom React hook is a function that starts with the word \u003ccode\u003euse\u003c/code\u003e and may call other hooks. Custom hooks let you reuse stateful logic between components without duplicating code or introducing complex patterns. You can learn more about custom hooks from \u003ca href=\"/posts/how-to-implement-hook-to-format-dates-in-react/#here-is-some-further-reading-for-you\"\u003ethe official React documentation (1)\u003c/a\u003e or \u003ca href=\"/posts/how-to-implement-hook-to-format-dates-in-react/#here-is-some-further-reading-for-you\"\u003ethis tutorial (2)\u003c/a\u003e.\u003c/p\u003e","title":"How to create a custom React hook for locale-specific date formatting"},{"content":"In the world of web development and application design, small details can make a big difference. One such detail is the proper capitalization of text, especially when dealing with user-generated content or dynamic data. Ensuring that text appears neat and consistent can significantly enhance the user experience. In this blog post, we\u0026rsquo;ll introduce you to a handy utility function called capitalizeEachWord that can help you achieve just that.\nWhat is capitalizeEachWord? The capitalizeEachWord function is a TypeScript utility that capitalizes the first letter of each word in a given string while keeping the rest of the letters in lowercase. This function also allows you to specify a word separator, which can be useful when dealing with text containing multiple words separated by a character or whitespace.\nHere\u0026rsquo;s the function code for reference:\nexport const capitalizeEachWord = (input: string, wordSeparator: string) =\u0026gt; input .split(wordSeparator) .map((word) =\u0026gt; word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(wordSeparator); How Does It Work? The capitalizeEachWord function works by following these steps:\nSplitting the Input String: It takes the input string and splits it into an array of words using the specified word separator.\nMapping: It then applies a mapping function to each word in the array. In this mapping function, it capitalizes the first letter of each word (using charAt(0).toUpperCase()) and converts the rest of the letters to lowercase (using slice(1).toLowerCase()).\nJoining: Finally, it joins the modified words back together using the same word separator to form a single string with each word capitalized correctly.\nExample Usage Let\u0026rsquo;s illustrate the usage of capitalizeEachWord with some examples:\nconst inputText1 = \u0026#34;hello world\u0026#34;; const inputText2 = \u0026#34;typeScript_is_awesome\u0026#34;; const separator = \u0026#34;_\u0026#34;; const result1 = capitalizeEachWord(inputText1, \u0026#34; \u0026#34;); const result2 = capitalizeEachWord(inputText2, separator); console.log(result1); // Output: \u0026#34;Hello World\u0026#34; console.log(result2); // Output: \u0026#34;TypeScript_Is_Awesome\u0026#34; Benefits of Using capitalizeEachWord\nConsistency: By ensuring that each word starts with a capital letter, you can achieve a consistent and professional look for your text, regardless of the input.\nImproved Readability: Capitalizing each word makes text more readable, especially for titles, headings, or user-generated content.\nCustomization: The function allows you to define the word separator, making it adaptable to various text formatting scenarios.\nEfficiency: capitalizeEachWord streamlines the process of capitalizing words in a string, saving you time and effort in manually formatting text.\nConclusion The capitalizeEachWord utility function is a simple yet powerful tool that can enhance the appearance and readability of your text in web development projects. Whether you\u0026rsquo;re working on a blog, a content management system, or an e-commerce platform, this function can help you achieve consistent and professional text formatting effortlessly. So, the next time you need to capitalize words in a string, remember to give capitalizeEachWord a try, and watch your text come to life!\n","permalink":"https://artosalminen.github.io/posts/how-to-capitalize-each-word-in-typescript/","summary":"\u003cp\u003eIn the world of web development and application design, small details can make a big difference. One such detail is the proper capitalization of text, especially when dealing with user-generated content or dynamic data. Ensuring that text appears neat and consistent can significantly enhance the user experience. In this blog post, we\u0026rsquo;ll introduce you to a handy utility function called \u003ccode\u003ecapitalizeEachWord\u003c/code\u003e that can help you achieve just that.\u003c/p\u003e\n\u003ch2 id=\"what-is-capitalizeeachword\"\u003eWhat is \u003ccode\u003ecapitalizeEachWord\u003c/code\u003e?\u003c/h2\u003e\n\u003cp\u003eThe \u003ccode\u003ecapitalizeEachWord\u003c/code\u003e function is a TypeScript utility that capitalizes the first letter of each word in a given string while keeping the rest of the letters in lowercase. This function also allows you to specify a word separator, which can be useful when dealing with text containing multiple words separated by a character or whitespace.\u003c/p\u003e","title":"How to capitalize each word of a string in TypeScript"},{"content":"Simply copy-paste the following one-liner to Chrome Dev Tools. To limit the results, set the reg exp placeholder to contain some sensible value, or replace the function call with return value true.\nArray.prototype.slice.call( document.getElementsByTagName(\u0026#39;a\u0026#39;), 0 ) .map(i =\u0026gt; i.href) .filter(i =\u0026gt; i.match(\u0026#39;reg exp placeholder\u0026#39;)) .forEach(i =\u0026gt; console.log(i)) Here is an explanation for what it does:\nSelects all the elements on the document. Converts the HTMLCollection object returned by getElementsByTagName into an Array by using the Array.prototype.slice method. The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included) where begin and end represent the index of items in that array. Here, it selects all the items starting from index 0 to the end of the array by passing 0 as the first argument to slice() method. Maps the href attribute of each of the selected elements into an array using the Array.prototype.map method. The resulting array will contain a list of all the href attributes of the elements. Filters the array obtained in the previous step by selecting only those elements that match a regular expression pattern. The pattern is a placeholder and should be replaced with an actual regular expression to match the desired elements. This is achieved using the Array.prototype.filter method. Loops through the resulting filtered array using the forEach() method and logs each element to the console using console.log() method. This will output the list of href attributes of the elements that match the given regular expression pattern to the console. ","permalink":"https://artosalminen.github.io/posts/how-to-scrape-all-links-from-website/","summary":"\u003cp\u003eSimply copy-paste the following one-liner to Chrome Dev Tools. To limit the results, set the reg exp placeholder to\ncontain some sensible value, or replace the function call with return value \u003ccode\u003etrue\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eArray.\u003cspan style=\"color:#a6e22e\"\u003eprototype\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eslice\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecall\u003c/span\u003e( document.\u003cspan style=\"color:#a6e22e\"\u003egetElementsByTagName\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e), \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e )\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  .\u003cspan style=\"color:#a6e22e\"\u003emap\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e =\u0026gt; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ehref\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  .\u003cspan style=\"color:#a6e22e\"\u003efilter\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e =\u0026gt; \u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ematch\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;reg exp placeholder\u0026#39;\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  .\u003cspan style=\"color:#a6e22e\"\u003eforEach\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e =\u0026gt; \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003ei\u003c/span\u003e))\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHere is an explanation for what it does:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eSelects all the \u003c!-- raw HTML omitted --\u003e elements on the document.\u003c/li\u003e\n\u003cli\u003eConverts the HTMLCollection object returned by getElementsByTagName into an Array by using the Array.prototype.slice method.\u003c/li\u003e\n\u003cli\u003eThe slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included) where begin and end represent the index of items in that array. Here, it selects all the items starting from index 0 to the end of the array by passing 0 as the first argument to slice() method.\u003c/li\u003e\n\u003cli\u003eMaps the href attribute of each of the selected \u003c!-- raw HTML omitted --\u003e elements into an array using the Array.prototype.map method. The resulting array will contain a list of all the href attributes of the \u003c!-- raw HTML omitted --\u003e elements.\u003c/li\u003e\n\u003cli\u003eFilters the array obtained in the previous step by selecting only those elements that match a regular expression pattern. The pattern is a placeholder and should be replaced with an actual regular expression to match the desired elements. This is achieved using the Array.prototype.filter method.\u003c/li\u003e\n\u003cli\u003eLoops through the resulting filtered array using the forEach() method and logs each element to the console using console.log() method. This will output the list of href attributes of the \u003c!-- raw HTML omitted --\u003e elements that match the given regular expression pattern to the console.\u003c/li\u003e\n\u003c/ol\u003e","title":"How to scrape all links from a website"},{"content":"Note: This works only from command line as user feedback is needed from keyboard to know which part of the version number to bump. It should not break pushing from a GUI tool (e.g. TortoiseGit), but you will not be prompted to update the version number. The positive thing is that the version change is done neatly as a separate commit.\nInstall husky as dev-dependency\nSee instructions.\nAdd the following script into new file .husky/pre-push\n#!/usr/bin/env sh . \u0026#34;$(dirname -- \u0026#34;$0\u0026#34;)/_/husky.sh\u0026#34; red=`tput setaf 1` green=`tput setaf 2` yellow=`tput setaf 3` reset=`tput sgr0` if sh -c \u0026#34;: \u0026gt;/dev/tty\u0026#34; \u0026gt;/dev/null 2\u0026gt;/dev/null; then # /dev/tty is available and usable # Allows us to read user input below, assigns stdin to keyboard exec \u0026lt; /dev/tty echo \u0026#34;${yellow}Enter version update you want to do:\u0026#34; echo \u0026#34; [p]atch (e.g. 0.1.3 -\u0026gt; 0.1.4)\u0026#34; echo \u0026#34; m[i]nor (e.g. 0.1.4 -\u0026gt; 0.2.0)\u0026#34; echo \u0026#34; m[a]jor (e.g. 0.2.0 -\u0026gt; 1.0.0)\u0026#34; echo \u0026#34; [n]one (version number not changed)${reset}\u0026#34; read update_type case \u0026#34;$update_type\u0026#34; in patch | [pP] ) yarn version --patch --no-commit-hooks ;; minor | [iI] ) yarn version --minor --no-commit-hooks ;; major | [aA]) yarn version --major --no-commit-hooks ;; none | [nN]) echo \u0026#34;No version change\u0026#34; ;; *) echo \u0026#34;${red}Invalid update type: $update_type ${reset}\u0026#34; ;; esac else # /dev/tty is not available echo \u0026#34;${red}[pre-push hook] Use command line to set the version number on push${reset}\u0026#34; fi Do the Git push from Terminal git push origin \u0026lt;your branch here\u0026gt;:\u0026lt;your remote branch here\u0026gt;\nOptionally, if you need to update Helm chart versions as well Change the script to include Helm Chart.xml update part.\n#!/usr/bin/env sh . \u0026#34;$(dirname -- \u0026#34;$0\u0026#34;)/_/husky.sh\u0026#34; red=`tput setaf 1` green=`tput setaf 2` yellow=`tput setaf 3` reset=`tput sgr0` version_updated=false if sh -c \u0026#34;: \u0026gt;/dev/tty\u0026#34; \u0026gt;/dev/null 2\u0026gt;/dev/null; then # /dev/tty is available and usable # Allows us to read user input below, assigns stdin to keyboard exec \u0026lt; /dev/tty echo \u0026#34;${yellow}Enter version update you want to do:\u0026#34; echo \u0026#34; [p]atch (e.g. 0.1.3 -\u0026gt; 0.1.4)\u0026#34; echo \u0026#34; m[i]nor (e.g. 0.1.4 -\u0026gt; 0.2.0)\u0026#34; echo \u0026#34; m[a]jor (e.g. 0.2.0 -\u0026gt; 1.0.0)\u0026#34; echo \u0026#34; [n]one (version number not changed)${reset}\u0026#34; read update_type case \u0026#34;$update_type\u0026#34; in patch | [pP] ) yarn version --patch --no-commit-hooks version_updated=true ;; minor | [iI] ) yarn version --minor --no-commit-hooks version_updated=true ;; major | [aA]) yarn version --major --no-commit-hooks version_updated=true ;; none | [nN]) echo \u0026#34;No version change\u0026#34; ;; *) echo \u0026#34;${red}Invalid update type: $update_type ${reset}\u0026#34; ;; esac else # /dev/tty is not available echo \u0026#34;${red}[pre-push hook] Use command line to set the version number on push${reset}\u0026#34; fi if [ \u0026#34;$version_updated\u0026#34; = true ] ; then the_version=$(git describe --abbrev=0) perl -pi -e \u0026#34;s/^appVersion:.*$/appVersion: ${the_version:1}/\u0026#34; helm/Chart.yaml git tag -d ${the_version} git add helm/Chart.yaml git commit -m \u0026#34;Set appVersion to ${the_version} in Helm Chart\u0026#34; -n git tag ${the_version} fi ","permalink":"https://artosalminen.github.io/posts/how-to-bump-version-number-on-git-push/","summary":"\u003cp\u003eNote: \u003cstrong\u003eThis works only from command line\u003c/strong\u003e as user feedback is needed from keyboard to know which part of the version number to bump. It should not break pushing from a GUI tool (e.g. TortoiseGit), but you will not be prompted to update the version number. The positive thing is that the version change is done neatly as a separate commit.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eInstall \u003ccode\u003ehusky\u003c/code\u003e as dev-dependency\u003c/p\u003e\n\u003cp\u003eSee \u003ca href=\"https://typicode.github.io/husky/#/?id=install\"\u003einstructions\u003c/a\u003e.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eAdd the following script into new file \u003cem\u003e.husky/pre-push\u003c/em\u003e\u003c/p\u003e","title":"How to bump version number on Git push for projects using npm"},{"content":" Install license-report (https://www.npmjs.com/package/license-report)\nnpm install -g license-report\nrun the script\nGet-ChildItem -Directory | foreach { $_ \u0026gt;\u0026gt; ./licenses.csv ; license-report --output=csv --only=prod --package=./$_/package.json \u0026gt;\u0026gt; ./licenses.csv } You can use the same idea to run other stuff in subdirectories. Just replace the command. Here is an example of git pull.\nGet-ChildItem -Directory -Force -Recurse *.git | ForEach-Object { cd $_.Parent.FullName; Write-Host $_.Parent.FullName; git pull } ","permalink":"https://artosalminen.github.io/posts/how-to-collect-npm-licenses-from-subdirectories/","summary":"\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eInstall \u003ccode\u003elicense-report\u003c/code\u003e (\u003ca href=\"https://www.npmjs.com/package/license-report\"\u003ehttps://www.npmjs.com/package/license-report\u003c/a\u003e)\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003enpm install -g license-report\u003c/code\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003erun the script\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-PowerShell\" data-lang=\"PowerShell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGet-ChildItem -Directory | \u003cspan style=\"color:#66d9ef\"\u003eforeach\u003c/span\u003e { $_ \u0026gt;\u0026gt; ./licenses.csv ; license-report --output=csv --only=prod --package=./$_/package.json \u0026gt;\u0026gt; ./licenses.csv }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eYou can use the same idea to run other stuff in subdirectories. Just replace the command. Here is an example of \u003ccode\u003egit pull\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-PowerShell\" data-lang=\"PowerShell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eGet-ChildItem -Directory -Force -Recurse *.git | ForEach-Object { cd $_.Parent.FullName; Write-Host $_.Parent.FullName; git pull }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"How to collect all npm licenses from multiple subdirectories with Powershell"},{"content":"In this blog post, we\u0026rsquo;ll go over how to update a specified item in a nested array inside a Mongoose document.\n##Setting up the environment\nFirst, let\u0026rsquo;s set up the environment by creating a Mongoose schema and model. Here\u0026rsquo;s an example schema that has a nested array of items:\nconst mongoose = require(\u0026#39;mongoose\u0026#39;); const schema = new mongoose.Schema({ items: [{ name: String, quantity: Number }] }); const Model = mongoose.model(\u0026#39;Model\u0026#39;, schema); ##Updating a specified item in a nested array\nTo update a specified item in a nested array, we can use the MongoDB $ operator along with the $set operator. The $ operator is used to specify which item in the array should be updated.\nHere\u0026rsquo;s an example of how to update the quantity field of the first item in the items array with the name equal to 'item1':\nModel.updateOne( {\u0026#39;items.name\u0026#39;: \u0026#39;item1\u0026#39;}, {\u0026#39;$set\u0026#39;: {\u0026#39;items.$.quantity\u0026#39;: 10}}, (err, result) =\u0026gt; { if (err) { console.error(err); } else { console.log(result); } } ); In this example, the Model.updateOne() method is used to update the first document that matches the query {'items.name': 'item1'}. The $ operator is used in the $set operator to specify that the update should be applied to the first item in the items array that matches the query condition. In this case, the quantity field of the item with name equal to 'item1' will be updated to 10.\nNote that the updateOne() method only updates the first document that matches the query. If there are multiple items in the items array with the name equal to 'item1', only the first one will be updated. To update all items that match the query, you can use the updateMany() method instead.\n","permalink":"https://artosalminen.github.io/posts/how-to-update-specified-item-in-nested-mongo-array-with-mongoose/","summary":"\u003cp\u003eIn this blog post, we\u0026rsquo;ll go over how to update a specified item in a nested array inside a Mongoose document.\u003c/p\u003e\n\u003cp\u003e##Setting up the environment\u003c/p\u003e\n\u003cp\u003eFirst, let\u0026rsquo;s set up the environment by creating a Mongoose schema and model. Here\u0026rsquo;s an example schema that has a nested array of items:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emongoose\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003erequire\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;mongoose\u0026#39;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eschema\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emongoose\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eSchema\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eitems\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ename\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e String,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003equantity\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e Number\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e});\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eModel\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emongoose\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003emodel\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;Model\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eschema\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e##Updating a specified item in a nested array\u003c/p\u003e","title":"How to update a specified item in a nested array with Mongoose"},{"content":"To access the Syonology NAS ports outside of your local network, you need to set up DDNS, a wildcard certificate, and a reverse proxy to support HTTPS access.\nDDNS Go to Control Panel / External Access / DDNS. Click Add.\nMake the following selections:\nService Provider: Synology Hostname: yourname.synology.me Username/Email: \u0026lt;your email\u0026gt; Password: \u0026lt;make it up\u0026gt; Exteral address: no need to change Wildcard certificate Go to Control Panel / Security / Certificate. Click Add.\nSelect replace existing certificate Select your Synology DDNS from the list Select Get a certificate from Let\u0026rsquo;s Encrypt Check \u0026ldquo;Set as default certificate\u0026rdquo; and click Next Configure the Let\u0026rsquo;s Encrypt certificate lke this\nDomain name: yourname.synology.me Email: \u0026lt;your email\u0026gt; Subject Alternative Name: *.yourname.synology.me and click Done Reverse Proxy Open the Synology Control Panel and navigate to Login Portal / Advanced / Reverse Proxy. Then click Create to create a proxy.\nNext, add a new rule for the proxy. Let\u0026rsquo;s use the rule to access Codeserver port with a subdomain hostname like codeserver.yourname.synology.me.\nSet up the General tab configuration as follows:\nReverse proxy name: codeserver.yourname.synology.me. Source Protocol: HTTPS Source hostname: codeserver.yourname.synology.me Port: 443 Enable HSTS: checked Access control profile: Not set Destination protocol: HTTP Hostname: localhost Port: 8377 Set up Custom Header tab headers:\nHeader name Value Upgrade $http_upgrade Connection $connection_upgrade ","permalink":"https://artosalminen.github.io/posts/how-to-set-up-wildcard-certificate-and-proxy-on-synology-nas/","summary":"\u003cp\u003eTo access the Syonology NAS ports outside of your local network, you need to\nset up DDNS, a wildcard certificate, and a reverse proxy to support HTTPS access.\u003c/p\u003e\n\u003ch2 id=\"ddns\"\u003eDDNS\u003c/h2\u003e\n\u003cp\u003eGo to \u003ccode\u003eControl Panel\u003c/code\u003e / \u003ccode\u003eExternal Access\u003c/code\u003e / \u003ccode\u003eDDNS\u003c/code\u003e. Click \u003cstrong\u003eAdd\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eMake the following selections:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eService Provider: Synology\u003c/li\u003e\n\u003cli\u003eHostname: \u003ccode\u003eyourname.synology.me\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eUsername/Email: \u003ccode\u003e\u0026lt;your email\u0026gt;\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003ePassword: \u003ccode\u003e\u0026lt;make it up\u0026gt;\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eExteral address: no need to change\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"wildcard-certificate\"\u003eWildcard certificate\u003c/h2\u003e\n\u003cp\u003eGo to \u003ccode\u003eControl Panel\u003c/code\u003e / \u003ccode\u003eSecurity\u003c/code\u003e / \u003ccode\u003eCertificate\u003c/code\u003e. Click \u003cstrong\u003eAdd\u003c/strong\u003e.\u003c/p\u003e","title":"How to access your Synology NAS services over public Web"},{"content":"The UID and GID values for default user on Synlogy NAS are usually 1026 and 100. There is a very simple way to check the values as follows:\nCreate a new Scheduled task asn User-defined script Name the script whatever you see fit Set it to be not repeating Set it to send run details to your email Write the script: id Not too complex, huh? Run it and soon enough, you will receive the email containing the UID and GID values. These values will be needed to install some Docker containers, for instance the Codeserver.\n","permalink":"https://artosalminen.github.io/posts/how-to-find-uid-and-gid-on-synology-nas/","summary":"\u003cp\u003eThe UID and GID values for default user on Synlogy NAS are usually 1026 and 100.\nThere is a very simple way to check the values as follows:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eCreate a new Scheduled task asn User-defined script\u003c/li\u003e\n\u003cli\u003eName the script whatever you see fit\u003c/li\u003e\n\u003cli\u003eSet it to be not repeating\u003c/li\u003e\n\u003cli\u003eSet it to send run details to your email\u003c/li\u003e\n\u003cli\u003eWrite the script:\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eid\n\u003c/code\u003e\u003c/pre\u003eNot too complex, huh?\u003c/li\u003e\n\u003cli\u003eRun it and soon enough, you will receive the email containing the UID and GID values.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThese values will be needed to install some Docker containers, for instance the Codeserver.\u003c/p\u003e","title":"How to find out your UID and GID on Synology NAS"},{"content":"Have you ever missed the VSCode on your tablet? Now there is a solution: run the VSCode on your NAS at home and access it with a browser. That will be possible when you run the Codeserver Docker container on your NAS.\nBefore you do this, you probably want to set up a wildcard certificate and a proxy server on your NAS. That will enable you to access the NAS outside your local network using HTTPS. How to do it, check this: How to Set Up Wildcard Certificate and Reverse Proxy on Synology NAS\nYou will also need to find out your UID and GID. Here are instructions to get those: How to find out your UID and GID on Synology NAS\nAnd after you have sorted those things, you can set up the Codeserver as follows:\nInstall Docker to the Synology NAS from Package Center Create a folder to map the codeserver files into, docker/codeserver works fine Open the Docker app, search for linuxserver/code-server from the registry, and download the image (latest) Set the port mapping as 8377:8443 Adjust the advanced settings as follows: PUID: \u0026lt;your UID\u0026gt; PGID: \u0026lt;your GID\u0026gt; PROXY_DOMAIN: codeserver.\u0026lt;your DDNS domain\u0026gt; PASSWORD: \u0026lt;your very complex password\u0026gt; SUDO_PASSWORD: \u0026lt;your very complex password\u0026gt; TZ: \u0026lt;your timezone, e.g Europe/Helsinki\u0026gt; Map the /config to the folder you created earlier (docker/codeserver) Set automatic restart Run the container Now you should be able to navigate to codeserver.\u0026lt;your DDNS domain\u0026gt; enter the password and enjoy your fresh VSCode on server.\n","permalink":"https://artosalminen.github.io/posts/how-to-set-up-codeserver-on-synology-nas/","summary":"\u003cp\u003eHave you ever missed the VSCode on your tablet? Now there is a solution: run the\nVSCode on your NAS at home and access it with a browser. That will be possible when you run\n\u003ca href=\"https://registry.hub.docker.com/r/linuxserver/code-server/\"\u003ethe Codeserver Docker container\u003c/a\u003e on your NAS.\u003c/p\u003e\n\u003cp\u003eBefore you do this, you probably want to set up a wildcard certificate and a proxy server on your NAS. That will\nenable you to access the NAS outside your local network using HTTPS. How to do it, check this:\n\u003ca href=\"https://artosalminen.github.io/posts/how-to-set-up-wildcard-certificate-and-proxy-on-synology-nas/\"\u003eHow to Set Up Wildcard Certificate and Reverse Proxy on Synology NAS\u003c/a\u003e\u003c/p\u003e","title":"How to Set Up a Codeserver on Synology NAS"},{"content":"You might want to access the configuration, for example to set the microservice configuration based on the values from the configuration service.\nOne way to do this is to create an application context from the AppModule.\nconst appContext = await NestFactory.createApplicationContext(BootstrapConfigModule) const configService = appContext.get(ConfigService) const SERVICE_PORT = configService.get(\u0026#39;SERVICE_PORT\u0026#39;) appContext.close() But a better idea is to use a \u0026ldquo;temporary\u0026rdquo; module to avoid double instantiation of the whole app. See a full example below.\nimport { Module } from \u0026#39;@nestjs/common\u0026#39; import { ConfigService } from \u0026#39;@nestjs/config\u0026#39; import { NestFactory } from \u0026#39;@nestjs/core\u0026#39; import { MicroserviceOptions, Transport } from \u0026#39;@nestjs/microservices\u0026#39; import { AppModule } from \u0026#39;./app.module\u0026#39; // Bootstrap configuration module is needed to avoid // doing the DI resolution twice @Module({ providers: [ConfigService], exports: [ConfigService], }) class BootstrapConfigModule {} async function bootstrap() { const appContext = await NestFactory.createApplicationContext(BootstrapConfigModule) const configService = appContext.get(ConfigService) const SERVICE_PORT = configService.get(\u0026#39;SERVICE_PORT\u0026#39;) appContext.close() const app = await NestFactory.createMicroservice\u0026lt;MicroserviceOptions\u0026gt;(AppModule, { transport: Transport.TCP, options: { port: SERVICE_PORT, }, }) await app.listen() } bootstrap() Why would you need to use the configuration service instead of process.env you might ask. Well, while that may work, it doesn\u0026rsquo;t offer any type safety or support for fallbacks.\n","permalink":"https://artosalminen.github.io/posts/how-to-use-config-service-in-nestjs-bootstrap/","summary":"\u003cp\u003eYou might want to access the configuration, for example to set the microservice\nconfiguration based on the values from the configuration service.\u003c/p\u003e\n\u003cp\u003eOne way to do this is to create an application context from the \u003ccode\u003eAppModule\u003c/code\u003e.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-typescript\" data-lang=\"typescript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eappContext\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eNestFactory\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecreateApplicationContext\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eBootstrapConfigModule\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econfigService\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eappContext\u003c/span\u003e.\u003cspan style=\"color:#66d9ef\"\u003eget\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eConfigService\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSERVICE_PORT\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econfigService\u003c/span\u003e.\u003cspan style=\"color:#66d9ef\"\u003eget\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;SERVICE_PORT\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eappContext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eclose\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBut a better idea is to use a \u0026ldquo;temporary\u0026rdquo; module to avoid double instantiation of the whole app.\nSee a full example below.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-typescript\" data-lang=\"typescript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eModule\u003c/span\u003e } \u003cspan style=\"color:#66d9ef\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;@nestjs/common\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eConfigService\u003c/span\u003e } \u003cspan style=\"color:#66d9ef\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;@nestjs/config\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eNestFactory\u003c/span\u003e } \u003cspan style=\"color:#66d9ef\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;@nestjs/core\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eMicroserviceOptions\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003eTransport\u003c/span\u003e } \u003cspan style=\"color:#66d9ef\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;@nestjs/microservices\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e { \u003cspan style=\"color:#a6e22e\"\u003eAppModule\u003c/span\u003e } \u003cspan style=\"color:#66d9ef\"\u003efrom\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;./app.module\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// Bootstrap configuration module is needed to avoid\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// doing the DI resolution twice\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e@Module\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eproviders\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#a6e22e\"\u003eConfigService\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eexports\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#a6e22e\"\u003eConfigService\u003c/span\u003e],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e})\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eBootstrapConfigModule\u003c/span\u003e {}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003easync\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ebootstrap() {\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eappContext\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eNestFactory\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecreateApplicationContext\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eBootstrapConfigModule\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econfigService\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eappContext\u003c/span\u003e.\u003cspan style=\"color:#66d9ef\"\u003eget\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eConfigService\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eSERVICE_PORT\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003econfigService\u003c/span\u003e.\u003cspan style=\"color:#66d9ef\"\u003eget\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;SERVICE_PORT\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eappContext\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eclose\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003econst\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eNestFactory\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003ecreateMicroservice\u003c/span\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003eMicroserviceOptions\u003c/span\u003e\u0026gt;(\u003cspan style=\"color:#a6e22e\"\u003eAppModule\u003c/span\u003e, {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003etransport\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003eTransport.TCP\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003eoptions\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003eport\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003eSERVICE_PORT\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  })\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eawait\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eapp\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elisten\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ebootstrap\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWhy would you need to use the configuration service instead of \u003ccode\u003eprocess.env\u003c/code\u003e you might ask.\nWell, while that may work, it doesn\u0026rsquo;t offer any type safety or support for fallbacks.\u003c/p\u003e","title":"How to use ConfigService in NestJS in application bootstrap"},{"content":"Let\u0026rsquo;s consider a situation where you have two collections, houses and people. Each house has a collection of key holders, which link to the persons collection with their IDs. Key holders list also holds information when the key was given for the identified person.\nIn bson, the situation in the database looks like this:\n{ \u0026#34;houses\u0026#34;: [ { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fba17c1c4566e57fafdcd7e\u0026#34;), \u0026#34;address\u0026#34;: \u0026#34;Main street 1\u0026#34;, \u0026#34;keyHolders\u0026#34;: [ { \u0026#34;keyDelivered\u0026#34;: \u0026#34;2022-02-02T02:02:02\u0026#34;, \u0026#34;personId\u0026#34;: \u0026#34;5fbb5ab778045a985690b5fc\u0026#34; }, { \u0026#34;keyDelivered\u0026#34;: \u0026#34;2021-01-01T01:01:01\u0026#34;, \u0026#34;personId\u0026#34;: \u0026#34;5fbb5ab778045a985690b5fd\u0026#34; } ] }, { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fba17c1c4566e57fafdcd7f\u0026#34;), \u0026#34;address\u0026#34;: \u0026#34;Broadway 3\u0026#34;, \u0026#34;keyHolders\u0026#34;: [ { \u0026#34;keyDelivered\u0026#34;: \u0026#34;1993-03-03T03:03:03\u0026#34;, \u0026#34;personId\u0026#34;: \u0026#34;5fbb5ab778045a985690b5fc\u0026#34; } ] } ], \u0026#34;persons\u0026#34;: [ { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;), \u0026#34;name\u0026#34;: \u0026#34;Jack Bauer\u0026#34;, }, { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fd\u0026#34;), \u0026#34;name\u0026#34;: \u0026#34;James Bond\u0026#34;, } ] } You can also do some mapping for the source list, for instance convert foreign keys from strings to ObjectIds.\nThe aggregate pipeline to do the trick looks like this:\n[ { $addFields: { mappedItems: { $map: { input: \u0026#34;$keyHolders\u0026#34;, in: { $mergeObjects: [ \u0026#34;$$this\u0026#34;, { personId: { $toObjectId: \u0026#34;$$this.personId\u0026#34; } } ] } } } } }, { $lookup: { from: \u0026#34;persons\u0026#34;, localField: \u0026#34;mappedItems.personId\u0026#34;, foreignField: \u0026#34;_id\u0026#34;, as: \u0026#34;itemsCollection\u0026#34; } }, { $project: { address: 1, keyHolders: { $map: { input: \u0026#34;$mappedItems\u0026#34;, as: \u0026#34;i\u0026#34;, in: { $mergeObjects: [ \u0026#34;$$i\u0026#34;, { $first: { $filter: { input: \u0026#34;$itemsCollection\u0026#34;, cond: { $eq: [ \u0026#34;$$this._id\u0026#34;, \u0026#34;$$i.personId\u0026#34; ] } } } } ] } } } } } ] And the result:\n[ { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fba17c1c4566e57fafdcd7e\u0026#34;), \u0026#34;address\u0026#34;: \u0026#34;Main street 1\u0026#34;, \u0026#34;keyHolders\u0026#34;: [ { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;), \u0026#34;keyDelivered\u0026#34;: \u0026#34;2022-02-02T02:02:02\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Jack Bauer\u0026#34;, \u0026#34;personId\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;) }, { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fd\u0026#34;), \u0026#34;keyDelivered\u0026#34;: \u0026#34;2021-01-01T01:01:01\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;James Bond\u0026#34;, \u0026#34;personId\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fd\u0026#34;) } ] }, { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fba17c1c4566e57fafdcd7f\u0026#34;), \u0026#34;address\u0026#34;: \u0026#34;Broadway 3\u0026#34;, \u0026#34;keyHolders\u0026#34;: [ { \u0026#34;_id\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;), \u0026#34;keyDelivered\u0026#34;: \u0026#34;1993-03-03T03:03:03\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Jack Bauer\u0026#34;, \u0026#34;personId\u0026#34;: ObjectId(\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;) } ] } ] And here is a Mongo playground link for you: https://mongoplayground.net/p/3s0sFfp7uIY\n","permalink":"https://artosalminen.github.io/posts/how-to-merge-nested-list-to-collection-with-mongo-aggregate/","summary":"\u003cp\u003eLet\u0026rsquo;s consider a situation where you have two collections, \u003ccode\u003ehouses\u003c/code\u003e and \u003ccode\u003epeople\u003c/code\u003e. Each house has a collection\nof key holders, which link to the persons collection with their IDs. Key holders list also holds information\nwhen the key was given for the identified person.\u003c/p\u003e\n\u003cp\u003eIn bson, the situation in the database looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;houses\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;_id\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003eObjectId(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5fba17c1c4566e57fafdcd7e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e)\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;address\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Main street 1\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;keyHolders\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;keyDelivered\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2022-02-02T02:02:02\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;personId\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;keyDelivered\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;2021-01-01T01:01:01\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;personId\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5fbb5ab778045a985690b5fd\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;_id\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003eObjectId(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5fba17c1c4566e57fafdcd7f\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e)\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;address\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Broadway 3\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;keyHolders\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;keyDelivered\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;1993-03-03T03:03:03\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;personId\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;persons\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;_id\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003eObjectId(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5fbb5ab778045a985690b5fc\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e)\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Jack Bauer\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;_id\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003eObjectId(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;5fbb5ab778045a985690b5fd\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e)\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;name\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;James Bond\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou can also do some mapping for the source list, for instance convert\nforeign keys from strings to ObjectIds.\u003c/p\u003e","title":"How to merge a nested list in Mongo document with another found in adjacent collection"},{"content":"If you need to copy a collection of documents from one Mongo collection to another, you can use the mongodump and mongorestore. But if the source database is in the same instance as the target, then you may be able to take a shortcut with mongosh.\nHere is the script to use:\ndb.\u0026lt;source collection\u0026gt;.find().forEach(function(d){ db.getSiblingDB(\u0026#39;\u0026lt;dest db\u0026gt;\u0026#39;)[\u0026#39;\u0026lt;dest collection\u0026gt;\u0026#39;].insert(d); }); For example, if you are copying all documents from current databases cars collection to a database called another-db\u0026rsquo;s collection vehicles:\ndb.cars.find().forEach(function(d){ db.getSiblingDB(\u0026#39;another-db\u0026#39;)[\u0026#39;vehicles\u0026#39;].insert(d); }); Of course, you can add filters to the find method if you want to copy only subset of documents.\n","permalink":"https://artosalminen.github.io/posts/how-to-copy-collection-to-another-mongo-db-in-mongosh/","summary":"\u003cp\u003eIf you need to copy a collection of documents from one Mongo collection to another, you can\nuse the \u003ccode\u003emongodump\u003c/code\u003e and \u003ccode\u003emongorestore\u003c/code\u003e. But if the source database is in the same instance\nas the target, then you may be able to take a shortcut with \u003ccode\u003emongosh\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eHere is the script to use:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003edb.\u0026lt;source collection\u0026gt;.find().forEach(function(d){ db.getSiblingDB(\u0026#39;\u0026lt;dest db\u0026gt;\u0026#39;)[\u0026#39;\u0026lt;dest collection\u0026gt;\u0026#39;].insert(d); });\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eFor example, if you are copying all documents from current databases \u003ccode\u003ecars\u003c/code\u003e collection to a database\ncalled \u003ccode\u003eanother-db\u003c/code\u003e\u0026rsquo;s collection \u003ccode\u003evehicles\u003c/code\u003e:\u003c/p\u003e","title":"How to Copy a Collection of Documents to Another Database with mongosh"},{"content":"So you want to set up a blog. Here is how to do it in 10 minutes or less. For me, it took a little more time, just to make sure for you it would not. And actually, even for me, most of the time was used to write this post.\nSo here we go, you have 40 for each step.\nCreate GitHub account if you do not have one yet Create new GitHub repository named \u0026lt;your GitHub username\u0026gt;.github.io On your local development environment, create a folder to work in, run npm install hugo-bin --save-dev to install Hugo CLI Clone your GitHub repository under the same directory git clone https://github.com/\u0026lt;your username\u0026gt;/\u0026lt;your username\u0026gt;.github.io.git Run npx hugo new site \u0026lt;your username\u0026gt;.github.io Navigate to the repository root directory cd \u0026lt;your username\u0026gt;.github.io Add a theme as Git Submodule: git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod Update config.toml by adding the theme = 'PaperMod' option (and add a name for the site while at it) Also update config.toml with your own baseUrl, e.g. baseURL = 'https://\u0026lt;your GitHub username\u0026gt;.github.io/' Run npx hugo new posts/initial-post.md Edit the initial post and when ready to publish, remove the draft tag Add the following into .github\\workflows\\gh-pages.yml file in the repository name: github pages on: push: branches: - main # Set a branch that will trigger a deployment pull_request: jobs: deploy: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: submodules: true # Fetch Hugo themes (true OR recursive) fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; # extended: true - name: Build run: hugo --minify - name: Deploy uses: peaceiris/actions-gh-pages@v3 if: github.ref == \u0026#39;refs/heads/main\u0026#39; with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public Create a branch named gh-pages in GitHub Check which branch is used for GitHub pages publishing in your GitHub repository, set it to gh-pages Set in GitHub Settings \u0026gt; Actions \u0026gt; General: Workflow permissions to Read and write permissions Push to Git remote using the main branch Make sure the deployment went smoothly, and enjoy the result in https://\u0026lt;your GitHub username\u0026gt;.github.io ","permalink":"https://artosalminen.github.io/posts/how-to-set-up-hugo-blog-in-github-pages/","summary":"\u003cp\u003eSo you want to set up a blog. Here is how to do it in 10 minutes or less. For me, it took a little more time, just to make sure for you it would not. And actually, even for me, most of the time was used to write this post.\u003c/p\u003e\n\u003cp\u003eSo here we go, you have 40 for each step.\u003c/p\u003e\n\u003col start=\"0\"\u003e\n\u003cli\u003eCreate GitHub account if you do not have one yet\u003c/li\u003e\n\u003cli\u003eCreate new GitHub repository named \u003ccode\u003e\u0026lt;your GitHub username\u0026gt;.github.io\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eOn your local development environment, create a folder to work in, run \u003ccode\u003enpm install hugo-bin --save-dev\u003c/code\u003e to install Hugo CLI\u003c/li\u003e\n\u003cli\u003eClone your GitHub repository under the same directory \u003ccode\u003egit clone https://github.com/\u0026lt;your username\u0026gt;/\u0026lt;your username\u0026gt;.github.io.git\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003enpx hugo new site \u0026lt;your username\u0026gt;.github.io\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eNavigate to the repository root directory \u003ccode\u003ecd \u0026lt;your username\u0026gt;.github.io\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eAdd a theme as Git Submodule: \u003ccode\u003egit submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eUpdate \u003ccode\u003econfig.toml\u003c/code\u003e by adding the \u003ccode\u003etheme = 'PaperMod'\u003c/code\u003e option (and add a name for the site while at it)\u003c/li\u003e\n\u003cli\u003eAlso update \u003ccode\u003econfig.toml\u003c/code\u003e with your own baseUrl, e.g. \u003ccode\u003ebaseURL = 'https://\u0026lt;your GitHub username\u0026gt;.github.io/'\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003enpx hugo new posts/initial-post.md\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eEdit the initial post and when ready to publish, remove the draft tag\u003c/li\u003e\n\u003cli\u003eAdd the following into \u003ccode\u003e.github\\workflows\\gh-pages.yml\u003c/code\u003e file in the repository\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ename: github pages\n\non:\npush:\n    branches:\n    - main  # Set a branch that will trigger a deployment\npull_request:\n\njobs:\n    deploy:\n        runs-on: ubuntu-22.04\n        steps:\n        - uses: actions/checkout@v3\n            with:\n            submodules: true  # Fetch Hugo themes (true OR recursive)\n            fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod\n\n        - name: Setup Hugo\n            uses: peaceiris/actions-hugo@v2\n            with:\n            hugo-version: \u0026#39;latest\u0026#39;\n            # extended: true\n\n        - name: Build\n            run: hugo --minify\n\n        - name: Deploy\n            uses: peaceiris/actions-gh-pages@v3\n            if: github.ref == \u0026#39;refs/heads/main\u0026#39;\n            with:\n            github_token: ${{ secrets.GITHUB_TOKEN }}\n            publish_dir: ./public\n\u003c/code\u003e\u003c/pre\u003e\u003c/li\u003e\n\u003cli\u003eCreate a branch named \u003ccode\u003egh-pages\u003c/code\u003e in GitHub\u003c/li\u003e\n\u003cli\u003eCheck which branch is used for GitHub pages publishing in your GitHub repository, set it to \u003ccode\u003egh-pages\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eSet in \u003cstrong\u003eGitHub Settings \u0026gt; Actions \u0026gt; General\u003c/strong\u003e: Workflow permissions to \u003ccode\u003eRead and write permissions\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003ePush to Git remote using the main branch\u003c/li\u003e\n\u003cli\u003eMake sure the deployment went smoothly, and enjoy the result in \u003ccode\u003ehttps://\u0026lt;your GitHub username\u0026gt;.github.io\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e","title":"How to set up a blog with Hugo and GitHub Pages in 10 minutes"}]