{"id":18818168,"url":"https://github.com/helloimkevo/udemydatingapp","last_synced_at":"2026-05-01T21:33:24.530Z","repository":{"id":149510978,"uuid":"614523781","full_name":"HelloImKevo/UdemyDatingApp","owner":"HelloImKevo","description":"Udemy course Dating App built with ASPNET Core and Angular infrastructure.","archived":false,"fork":false,"pushed_at":"2024-01-23T21:07:22.000Z","size":9170,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-10-27T16:36:45.799Z","etag":null,"topics":["angular","csharp","dotnet","udemy-course-project"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HelloImKevo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-15T19:01:17.000Z","updated_at":"2024-03-23T12:15:59.000Z","dependencies_parsed_at":null,"dependency_job_id":"aaab3807-ddc1-49c2-a526-985409431cc8","html_url":"https://github.com/HelloImKevo/UdemyDatingApp","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/HelloImKevo/UdemyDatingApp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HelloImKevo%2FUdemyDatingApp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HelloImKevo%2FUdemyDatingApp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HelloImKevo%2FUdemyDatingApp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HelloImKevo%2FUdemyDatingApp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HelloImKevo","download_url":"https://codeload.github.com/HelloImKevo/UdemyDatingApp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HelloImKevo%2FUdemyDatingApp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32513684,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","csharp","dotnet","udemy-course-project"],"created_at":"2024-11-08T00:15:25.793Z","updated_at":"2026-05-01T21:33:24.457Z","avatar_url":"https://github.com/HelloImKevo.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"![dotnet-version](https://img.shields.io/badge/.NET-7-purple)\n![angular-version](https://img.shields.io/badge/Angular-14.3-red)\n[![CI workflow](https://img.shields.io/github/actions/workflow/status/HelloImKevo/UdemyDatingApp/docker-push.yml?branch=master\u0026label=ci\u0026logo=github)](https://github.com/HelloImKevo/UdemyDatingApp/actions?workflow=docker-push)\n\n# UdemyDatingApp\nUdemy course Dating App built with ASPNET Core and Angular infrastructure.\n\nFull course solution designed by the instructor available here:  \nhttps://github.com/TryCatchLearn/DatingApp\n\n\n# Dependencies\nDownload and install .NET:  \nhttps://dotnet.microsoft.com/en-us/download\n\nIf you are using Mac Apple Silicon Chip, install\n.NET 6.0 for Arm64.\n\nDownload and install Node JS:  \nhttps://nodejs.org/en/\n\nEdit: ~~You can use version 18.15.0 LTS~~ For this Udemy course you need to use Node\nversion 16, which is available for download here:  \nhttps://nodejs.org/download/release/v16.10.0/\n\n### NuGet: Dotnet Entity Framework\n\nhttps://www.nuget.org/packages/dotnet-ef/  \n\nRun:\n```shell\ndotnet tool install --global dotnet-ef --version 7.0.4\n```\n\n## The Fundamentals\n\nConfigure your Git Access. I recommend using Git Credential Manager Core:\nhttps://github.com/git-ecosystem/git-credential-manager/blob/release/docs/install.md\n\nRun:\n```shell\ngit credential-manager configure\n\ngit config --show-origin --get credential.helper\n```\n\nYou can erase existing OSX Keychain credentials with:\n```shell\ngit config --unset credential.helper\n\ngit credential-osxkeychain erase\n\n[Press Return]\n```\n\nSee: https://docs.github.com/en/get-started/getting-started-with-git/updating-credentials-from-the-macos-keychain\n\nIf you're running into issues, make sure you aren't using a repo that was originally \nconfigured for SSH, and you are now attempting to use the same repo with Git \nCredential Manager - this will be significantly more complex to configure.\n\n\n# Screenshot References\n\n| Matches |\n| :---: |\n| ![Matches](Screenshots/app-01.png) |\n\n| Edit Profile |\n| :---: |\n| ![Edit Profile](Screenshots/app-06.png) |\n\n| Online Notifications |\n| :---: |\n| ![Online Notifications](Screenshots/app-10.png) |\n\n| User Message Thread |\n| :---: |\n| ![User Message Thread](Screenshots/app-09.png) |\n\n\n# Debugging with VS Code\n\n![Debugging 01](Screenshots/debug-01.png)\n\n![Debugging 02](Screenshots/debug-02.png)\n\n![Debugging 03](Screenshots/debug-03.png)\n\n![Debugging 04](Screenshots/debug-04.png)\n\n\n# Helpful Quick References\n\n## Angular App and API Quick Start\n\nIn one VS Code Terminal, `cd API/` then run:\n```\ndotnet watch --no-hot-reload\n```\n\nAnd in another Terminal instance, `cd client` then run:\n```\nkill -9  $(lsof -t -i:4200);ng serve\nng serve\n```\n\nNow both services (API server and client app) will be running. Open this URL in a browser:  \nhttps://localhost:4200/\n\n\n## Troubleshooting Dotnet Runtime\n\nIf you encounter this error:\n```\nSystem.IO.IOException: Failed to bind to address https://127.0.0.1:5001: address already in use.\n```\n\nYou can try running:\n```\nlsof -i:5001\n```\n\nExample output:\n```\nGoogle    36572 john   70u  IPv6 0x1223c1a0ae0e2ced  0t0  TCP localhost:60565-\u003elocalhost:commplex-link (ESTABLISHED)\nAPI       96825 john  244u  IPv4 0x1223c1aa43f9eee5  0t0  TCP localhost:commplex-link (LISTEN)\nAPI       96825 john  245u  IPv6 0x1223c1a0ae0e3bed  0t0  TCP localhost:commplex-link (LISTEN)\nAPI       96825 john  256u  IPv6 0x1223c1a0ae0c436d  0t0  TCP localhost:commplex-link-\u003elocalhost:60565 (ESTABLISHED)\n```\n\nThen run `kill -9 \u003cpid\u003e` to manually kill the leftover \"API\" processes. In the above example, it \nshould be: `kill -9 96825`.\n\n\n## Visual Studio Code Tips and Tricks\n\nOpen Settings, search for \"exclude\", under \"Files: Exclude\", click on **Add Pattern**. Type\n`**/bin` and click **OK**. And do the same for `**/obj`. This will hide these folders from the\nSolution Explorer, since we won't interact with them very often.\n\nWithin Settings, search for \"bracket\" and make sure these two settings are Enabled:\n- Auto Closing Brackets - Always\n- Bracket Pair Colorization: Enabled - Checked\n- Bracket Pair Colorization: Independent Color Pool Per Bracket Type - Unchecked\n- Guides: Bracket Pairs - True\n\nOpen the **Command Palette** with: `SHIFT + CMD + P` (MacOS).\n\nOpen the editor's **More Actions...** contextual menu with `CMD + .` (MacOS); this will provide \nyou with helpful quick actions like \"Remove unnecessary usings\", or \"Generate constructor\".\n\nOpen the **Keyboard Shortcuts** window under Settings, then click on the small icon in the\ntop-right corner with tooltip \"Open Keyboard Shortcuts (JSON)\" (the icon looks like a piece\nof paper with a folded corner, and a circular arrow on the left). In the `keybindings.json`\nfile, add this entry:\n\n```json\n{\n    \"key\": \"shift shift\",\n    \"command\": \"workbench.action.quickOpen\"\n}\n```\n\nSave the `keybindings.json` file and then close it. Now, when you double-tap SHIFT, it will open\nup a sort of \"Global Object Search\" form field, and you can type the name of an entity, like\nour `AppUser.cs`, and then press RETURN to open the file. Super-handy to have!\n\nMore details:\nhttps://stackoverflow.com/questions/29613191/intellij-shift-shift-shortcut-in-visual-studio-global-search  \n\nUnder **Settings \u003e CodeLens**, turn off \"Show Main Code Lens\". It adds extraneous noise to every \nmethod signature in the editor UI, with a bunch of \"N references\" indicators everywhere.\n\n## Dotnet Commands\n\nList currently installed tools:\n```shell\ndotnet tool list -g\n```\n\n\n## A Walking Skeleton\n\u003e A Walking Skeleton is a tiny implementation of the system that performs a small \n\u003e end-to-end function. It need not use the final architecture, but it should link \n\u003e together the main architectural components.\n\u003e \n\u003e The architecture and the functionality can then evolve in parallel.\n\u003e \n\u003e - Alistair Cockburn\n\nLEARNING GOALS\nImplement the basic API functionality and have an introductory understanding of:\n\n1. Using the dotnet CLI\n2. API Controllers and Endpoints\n3. Entity Framework\n4. The API Project structure\n5. Configuration and Environment variables\n6. Source control\n\n\n## Creating a New Project\nRun:\n```shell\ndotnet --info\n\ndotnet -h\n```\n\nCreate new solution file and project file:\n```shell\ndotnet new sln\n\ndotnet new webapi -n API\n\nls\n\ndotnet sln -h\n\ndotnet sln add API/\n\ndotnet sln list\n```\n\nOpen Visual Studio Code, do \"Show All Commands\" (SHIFT + CMD + P), type \"PATH\", \nselect option: **Shell command: Install 'code' command in PATH.**\n\nIf you run into a \"Permission Denied\" error, try uninstalling it first.\n\nOpen project by running this in the Terminal:\n```shell\ncode .\n```\n\n\n# Visual Studio Code Setup\n\nGo to the Extensions tab (looks like 4 Tetris blocks), and search for \"C#\".\n\nInstall the **C# Extension** verified by Microsoft (powered by OmniSharp); you don't\nneed to install any \"Recommended Extensions\" right now.\n\nIt's recommended to turn on Auto Save functionality, under File menu (click \non \"Auto Save\").\n\nWith the same C# Extension still visible, Right click on the Extension and click on \n\"Extension Settings\". It might say \"No Settings Found\" -- no worries. Open the \n**Command Palette** (SHIFT + CMD + P), and search for \"Reload\" and click on the \n\"Reload Window\" option.\n\nUnder the Extension Settings, scroll down (while on the \"User\" tab) and find these\nsettings:\n1. Omnisharp: Enable Async Completion (EXPERIMENTAL)\n2. Omnisharp: Enable Import Completion\n3. Omnisharp: Organize Imports On Format\n\nClick the checkboxes to enable all of these settings.\n\nLook for \"Private Member Prefix\" and input `_` (underscore) and turn off the \n\"Use This For Ctor Assignments\" checkbox.\n\nYou should get a Visual Studio Code notification prompting you to **Restart OmniSharp**, \ngo ahead and accept that to restart the Extension.\n\nYou can \"Toggle Terminal\" with CTRL + Backtick (aka, Grave Accent; the key to the \nleft of the \"1\" on your keyboard numbers row).\n\nBack in the Extensions area, search for **C# Extensions**, and install the Extension \nmaintained by **JosKreativ**. And, install **Material Icon Theme** by Philipp Kief (after) \nthis is installed, you need to click on \"Material Icon Theme\" in the Command Palette \narea (it should automagically show up). You might need to click on **Set File Icon Theme**,\ndepending on your operating environment.\n\nOptionally: Under Preferences \u003e Settings, search for \"compact\", and turn off\n\"Explorer: Compact Folders\".  \n\nAlso install **GitLens - Git supercharged** by GitKraken if you want Git line annotations.  \n\nOpen the **Command Palette** (SHIFT + CMD + P) and type \".net\", and click on:\n\".NET: Generate Assets for Build and Debug\". This will create the `.vscode` directory\nand the configuration files: `launch.json` and `tasks.json`.\n\n\n## Running Project for the First Time\nToggle the **Terminal** (CTRL + Backtick), change directory to `API/` and then run:\n```shell\ndotnet run\n```\n\nOn MacOS, you will probably be prompted with a Keychain request and will require your\npassword. Here's the output from the Terminal for reference:\n```\nBuilding...\nwarn: Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer[5]\n      The application is trying to access the ASP.NET Core developer certificate key. \n      A prompt might appear to ask for permission to access the key. When that happens, \n      select 'Always Allow' to grant 'dotnet' access to the certificate key in the future.\n```\n\nIf you try to open the URL specified in your `Properties/launchSettings.json` file, like\nhttps://localhost:7183/ -- you're not gonna see anything interesting right now, since our\n\"API\" controller doesn't have any associated UI and it's just listening for API endpoints; \nyou'll see something like this in your Browser:\n```\nThis localhost page can’t be found\nNo webpage was found for the web address: https://localhost:7183/\nHTTP ERROR 404\n```\n\nExamine the `WeatherForecastController.cs` (after modifications to the codebase, this file \nwill no longer exist), and look at the `[Route]` property -- the String, `\"Controller\"` gets\nremoved from the class name, to build the URL for the endpoint.\n\n\n### API URL in Browser\nSo the active URL for the project at this point in time is actually:  \nhttps://localhost:5001/WeatherForecast  \n\nWhen you view this URL, the browser should show the JSON contents of an\n`IEnumerable\u003cWeatherForecast\u003e` like this:\n```js\n[{\n    \"date\": \"2023-03-16T17:54:14.96377-04:00\",\n    \"temperatureC\": 12,\n    \"temperatureF\": 53,\n    \"summary\": \"Cool\"\n}, {\n    \"date\": \"2023-03-20T17:54:14.96394-04:00\",\n    \"temperatureC\": 39,\n    \"temperatureF\": 102,\n    \"summary\": \"Chilly\"\n}]\n```\n\nLet's shut it down (CTRL + C) and then inspect the Help for dotnet: `dotnet run -h`.  \n\nBased on the help docs, we can specify which launch profile we want to use, with a command\nlike: `dotnet run -lp \"API\"`. Note: You may run into a developer certificate issue, which\nwill be described in the **Terminal**; it should provide some suggested remediation steps, \nsuch as running `dotnet dev-certs https --clean`, which may require elevated privileges in\nthe Windows operating environment.  \n\nIn the `launchSettings.json` file, let's change the localhost ports within the `\"applicationUrl\"` \nelement to 5001 and 5000, something like this:\n```\n\"applicationUrl\": \"https://localhost:5001;http://localhost:5000\"\n```\n\nSwagger won't be used for this project, so you can remove the `\"launchUrl\": \"swagger\"` entry. \nIf you want to see a demonstration of it, you can add this property back, for reference:\n```js\n\"profiles\": {\n  \"API\": {\n    \"commandName\": \"Project\",\n    \"dotnetRunMessages\": true,\n    \"launchBrowser\": true,\n    \"launchUrl\": \"swagger\",\n    \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\",\n    \"environmentVariables\": {\n      \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n    }\n  },\n```\n\nAnd then navigate to: https://localhost:5001/swagger/index.html to see the automagically \ngenerated API documentation for the project. We're gonna be using a different tool to test \nour application.  \n\nInside our `API.csproj` file, it lists packages that we have installed. Let's get rid of\nthis `\u003cItemGroup\u003e` element and its child entries, to simplify things:\n```xml\n\u003cItemGroup\u003e\n  \u003cPackageReference Include=\"Swashbuckle.AspNetCore\" Version=\"6.2.3\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\nAfter installing or uninstalling packages, you need to then run: `dotnet restore`. This\nwill result in some Project Errors we will fix now. Open `appsettings.Development.json` and\nlet's bump the AspNetCore level to `\"Information\"`:\n```js\n{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Information\"\n    }\n  }\n}\n```\n\nOpen the `Program.cs` file, and clean up the services container to look like this:\n```cs\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add services to the container.\nbuilder.Services.AddControllers();\n\nvar app = builder.Build();\n\n// Configure the HTTP request pipeline.\napp.MapControllers();\n\napp.Run();\n```\n\nNow let's run `dotnet watch run`. You should see a message like: `Hot reload enabled`. This feature \nto \"Hot Reload\" and re-deploy changes as you make them in the Editor can be finicky (sometimes it \ncauses more problems than it solves).\n\nAfter successfully running `dotnet watch run` or `dotnet run`, navigate to:  \nhttps://localhost:5001/api/users\n\nYou should see this output in the Browser UI:\n```json\n[{\n  \"id\": 1,\n  \"userName\": \"Bob\"\n}, {\n  \"id\": 2,\n  \"userName\": \"Tom\"\n}, {\n  \"id\": 3,\n  \"userName\": \"Jane\"\n}]\n```\n\nFor API testing, it's more efficient to use Postman. Go ahead and create a new Postman Workspace \nnamed \"UdemyDatingApp\", and add a Collection called \"Users\", and add a Request called \"Get Users\".  \n\nSet the request URL to: https://localhost:5001/api/users  \n\nIf you get an error like \"Could not get response -- SSL Error: Unable to verify the first certificate\",\ngo to Settings (Preferences) and turn off \"SSL Certificate Verification\".\n\n\n# Entity Frameworks\n\nWhat is it? An Object Relational Mapper (ORM), which translates our code into SQL commands that\nupdate our tables in the database. Prior to .NET 3.5, we often used to write ADO.NET code to\nsave or retrieve data from underlying database. It was a cumbersome and error-prone process.\n\nEntity Framework automates a lot of these database-related activities for our application.\nWhen we introduce our Entity Framework, we need to create an important class that derives from\nthe `DbContext` class. This acts as a bridge between our domain and the database. This will\nenable us to use LINQ (Link) queries.\n\nEntity Framework works with database providers. The one we're going to use purely for development\nis SQLite, which does require a database server -- it's not production-worthy, but it is\nlightweight and portable, suitable for development (and something like SQL Server is not\ncross-platform).\n\nEntity framework enables querying the database with **LINQ**, and it supports **Change Tracking**\nof entities, and allows us to **Save** our database. It also gives us optimistic **Concurrency**\nto protect overwriting changes from another user, and database **Transactions**. And it supports\n**Caching** and built-in **Conventions** (which govern how the model will be mapped to a database\nschema), and **Configurations** for entities. It also offers us **Migrations**, to make our\ndatabase management more robust.\n\n\n## Installing Nuget\n\nUnder Extensions, search for and install \"NuGet Gallery\" by pcislo.\n\nThen, open the **Command Palette** (SHIFT + CMD + P), and search for \"NuGet\" and click on the\noption that reads \"NuGet: Open NuGet Gallery\".\n\nLook for \"Microsoft.EntityFrameworkCore\" by Microsoft, and scroll down to the `.Sqlite.Core`\npackage. Select the version of .NET you are using, you can run `dotnet --version` in the \n**Terminal**, and install the SQLite Core entity framework in the `API.csproj` file.\n\nIf you look at the `API.csproj` file, we should see a new entry like this:\n```xml\n\u003cItemGroup\u003e\n  \u003cPackageReference Include=\"Microsoft.EntityFrameworkCore.Sqlite.Core\" Version=\"7.0.4\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\nBack in the NuGet Gallery, look for \"Microsoft.EntityFrameworkCore.Design\" by Microsoft, and\ninstall that also (using the same dotnet version). Then, the CS Project file should look like this:\n```xml\n\u003cItemGroup\u003e\n  \u003cPackageReference Include=\"Microsoft.EntityFrameworkCore.Design\" Version=\"7.0.4\"\u003e\n    \u003cIncludeAssets\u003eruntime; build; native; contentfiles; analyzers; buildtransitive\u003c/IncludeAssets\u003e\n    \u003cPrivateAssets\u003eall\u003c/PrivateAssets\u003e\n  \u003c/PackageReference\u003e\n  \u003cPackageReference Include=\"Microsoft.EntityFrameworkCore.Sqlite.Core\" Version=\"7.0.4\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\nWe're also going to need \"Microsoft.AspNetCore.Identity.EntityFrameworkCore\" by Microsoft.\nInstall version `7.0.0`.\n\n### Oops! Something isn't right...\nWe installed the wrong SQLite framework -- we wanted simply `Sqlite` instead of `Sqlite.Core`... 😩\nDelete the `\u003cPackageReference Include=\"Microsoft.EntityFrameworkCore.Sqlite.Core\" Version=\"7.0.4\" /\u003e`\npackage reference, then go back to NuGet Gallery and install:\n```\nMicrosoft.EntityFrameworkCore.Sqlite by Microsoft\n```\n\nDouble-check the `API.csproj` file to confirm we are ready to rock-and-roll. 🤟\n\n\n### NuGet: Installing Identity Model Tokens JWT\n\nOpen the **Command Palette** (SHIFT + CMD + P), and search for \"NuGet\" and click on the\noption that reads \"NuGet: Open NuGet Gallery\".\n\nLook for \"system.identity\", and install \"System.IdentityModel.Tokens.Jwt\" by Microsoft.\nI selected version `6.24.0`.\n\nLook for \"microsoft.aspnetcore.authentication\", and install \n\"Microsoft.AspNetCore.Authentication.JwtBearer\" by Microsoft.\nI selected version `6.0.10`.\n\n\n### NuGet: Installing AutoMapper\n\nOpen the **Command Palette** (SHIFT + CMD + P), and search for \"NuGet\" and click on the\noption that reads \"NuGet: Open NuGet Gallery\".\n\nLook for \"automapper\", and install \"AutoMapper.Extensions.Microsoft.DependencyInjection\" \nby Jimmy Bogard. I selected version `12.0.0`.\n\n\n## Installing Angular Language Service\n\nUnder Extensions, search for and install \"Angular Language Service\" by Angular.\n\nThis extension provides a rich editing experience for Angular templates, both inline and external \ntemplates including:\n- Completions lists\n- AOT Diagnostic messages\n- Quick info\n- Go to definition\n\n\n## Entity Framework DbContext\n\nFrom the docs:\n\n\u003e A DbContext instance represents a session with the database and can be used to query \n\u003e and save instances of your entities. DbContext is a combination of the Unit Of Work and \n\u003e Repository patterns.\n\nRun:\n```shell\ndotnet ef migrations add InitialCreate -o Data/Migrations\n```\n\nOutput:\n```shell\nBuild started...\nBuild succeeded.\nDone. To undo this action, use 'ef migrations remove'\n```\n\nThen run:\n```shell\ndotnet ef database update\n```\n\nOutput snippets:\n```shell\ninfo: Microsoft.EntityFrameworkCore.Database.Command[20101]\n      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']\n      CREATE TABLE \"__EFMigrationsHistory\" (\n          \"MigrationId\" TEXT NOT NULL CONSTRAINT \"PK___EFMigrationsHistory\" PRIMARY KEY,\n          \"ProductVersion\" TEXT NOT NULL\n      );\n\nSELECT COUNT(*) FROM \"sqlite_master\" WHERE \"name\" = '__EFMigrationsHistory' AND \"type\" = 'table';\n\ninfo: Microsoft.EntityFrameworkCore.Database.Command[20101]\n      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']\n      CREATE TABLE \"Users\" (\n          \"Id\" INTEGER NOT NULL CONSTRAINT \"PK_Users\" PRIMARY KEY AUTOINCREMENT,\n          \"UserName\" TEXT NULL\n      );\n```\n\n\n## Installing a SQLite Browser Extension\n\nOpen the Extensions tool window, search for **SQLite**, and install the Extension \nmaintained by **alexcvzz**.\n\nThen, open the **Command Palette** (SHIFT + CMD + P), and search for \"sqlite\" and click on the\noption that reads \"SQLite: Open Database\". Choose our database file: `API/datingapp.db`.\n\nThe UX design for this Extension has changed in subsequent versions of Visual Studio. As of this\nwriting, there should now be a \"SQLITE EXPLORER\" expandable menu at the bottom of the\n**Solution Explorer** tool window (on the left sidebar).\n\n\n### Inserting Test Values in SQL Users Table\n\nUnder the **SQLITE EXPLORER** menu, right-click on the \"Users\" table, and click on the\n\"New Query [Insert]\" option. You should see this SQL statement in the editor window:\n```sql\n-- SQLite\nINSERT INTO Users (Id, UserName)\nVALUES ();\n```\n\nCopy the complete statement a few times, and then populate `VALUES()` with values:\n```\n1, \"Bob\"\n2, \"Tom\"\n3, \"Jane\"\n```\n\nHighlight the three queries in the editor, right-click, then click on the \"Run Selected Query\"\noption. If you click the \"Run\" arrow next to the Users table, you should see output like:\n```\n+----+----------+\n| Id | UserName |\n+----+----------+\n|  1 | Bob      |\n|  2 | Tom      |\n|  3 | Jane     |\n+----+----------+\n```\n\n\n## Multithreading\n\nWe wrap the `ActionResult` return types with `Task\u003c\u003e`, with documentation that reads:\n\u003e Represents an asynchronous operation that can return a value.\n\nAnd we pair this with the `await` keyword:\n\u003e This async method lacks 'await' operators and will run synchronously. Consider using the \n\u003e 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work \n\u003e on a background thread.\n\nIf you get an error like this when testing the API:\n```\nMicrosoft.AspNetCore.Routing.Matching.AmbiguousMatchException: \n  The request matched multiple endpoints. Matches: \n\nAPI.Controllers.UsersController.GetUser (API)\nAPI.Controllers.UsersController.GetUser (API)\n  at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)\n  at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, CandidateState[] candidateState)\n  at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.MatchAsync(HttpContext httpContext)\n  at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)\n  at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)\n\nHEADERS\n=======\nAccept: */*\nConnection: keep-alive\nHost: localhost:5001\nUser-Agent: PostmanRuntime/7.31.1\nAccept-Encoding: gzip, deflate, br\n```\n\nIt is likely a failure relating to the \"Hot Reload\" feature used by `dotnet watch run`. You'll\nneed to shut down the dotnet runtime and restart it.\n\n\n# Angular Framework\n\n## Learning Goals\n\nComplete the walking skeleton and have an introductory understanding of:\n1. Using the Angular CLI\n2. How to create a new Angular app\n3. The Angular project files\n4. The Angular bootstrap process\n5. Using the Angular HTTP Client Service\n6. Running an Angular app over HTTPS\n7. How to add packages using NPM\n\nWe will be using Angular to create a SPA (Single Page Application).\n\n\n## Angular CLI Initial Setup\n\nCheck versions with:\n```shell\nnode --version\n\nnpm --version\n```\n\nThen let's install Angular version 14:\n```shell\nnpm install -g @angular/cli@14\n```\n\nIf you get this sort of error:\n```\nnpm ERR! code EACCES\nnpm ERR! syscall mkdir\nnpm ERR! path /usr/local/lib/node_modules/@angular\nnpm ERR! errno -13\nnpm ERR! Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/@angular'\nnpm ERR!  [Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/@angular'] {\nnpm ERR!   errno: -13,\nnpm ERR!   code: 'EACCES',\nnpm ERR!   syscall: 'mkdir',\nnpm ERR!   path: '/usr/local/lib/node_modules/@angular'\nnpm ERR! }\nnpm ERR! \nnpm ERR! The operation was rejected by your operating system.\nnpm ERR! It is likely you do not have the permissions to access this file as the current user\nnpm ERR! \nnpm ERR! If you believe this might be a permissions issue, please double-check the\nnpm ERR! permissions of the file and its containing directories, or try running\nnpm ERR! the command again as root/Administrator.\n```\n\nYou may need to run the command again, prefixed with `sudo`\n\nAfter successful installation, run this to see the Angular details:\n```\nng version\n```\n\nWhich will have output like:\n```\n     _                      _                 ____ _     ___\n    / \\   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|\n   / △ \\ | '_ \\ / _` | | | | |/ _` | '__|   | |   | |    | |\n  / ___ \\| | | | (_| | |_| | | (_| | |      | |___| |___ | |\n /_/   \\_\\_| |_|\\__, |\\__,_|_|\\__,_|_|       \\____|_____|___|\n                |___/\n    \n\nAngular CLI: 14.2.11\nNode: 16.10.0\nPackage Manager: npm 7.24.0 \nOS: darwin arm64\n\nAngular: \n... \n\nPackage                      Version\n------------------------------------------------------\n@angular-devkit/architect    0.1402.11 (cli-only)\n@angular-devkit/core         14.2.11 (cli-only)\n@angular-devkit/schematics   14.2.11 (cli-only)\n@schematics/angular          14.2.11 (cli-only)\n```\n\nNext run:\n```\nng new client\n```\nWhich will create a `client/` directory alongside our existing `API/` folder.\n\nChoose **Yes** for \"Would you like to add Angular routing?\", and choose **CSS** for \nour stylesheet format.\n\nIf you run into errors, try uninstalling the CLI and re-creating the `client` with:\n```\nrm -r client/\n\nnpm cache clean --force\nnpm uninstall -g @angular/CLI\n\nsudo chown -R 505:20 \"/usr/local/lib/node_modules\"\n\nsudo npm install -g @angular/cli@14\n\nnpm -v\n\nng version\n\nsudo ng new client\n```\n\nYou should eventually see:\n```\nCREATE client/src/app/app.component.css (0 bytes)\nCREATE client/src/app/app.component.html (23115 bytes)\nCREATE client/src/app/app.component.spec.ts (1073 bytes)\nCREATE client/src/app/app.component.ts (210 bytes)\n✔ Packages installed successfully.\n    Directory is already under version control. Skipping initialization of git.\n```\n\nThis might take a couple of minutes to download all the dependencies -- be patient 😉\n\nTo run the Angular client app, navigate to the `client` dir with `cd client`,\nand then run `ng serve`.\n\nIf you run into `Error: EACCES: permission denied, mkdir` errors, then try manually\ncreating the `.angular` directory and elevating the permissions for the `client` dir:\n```\nsudo chmod -R 755 client/\nsudo chmod -R 775 /usr/local/lib/node_modules/\n\ncd client\nmkdir .angular\n```\n\nWhen you successfully run `ng serve`, you should see terminal output like this:\n```\n✔ Browser application bundle generation complete.\n\nInitial Chunk Files   | Names         |  Raw Size\nvendor.js             | vendor        |   2.12 MB | \npolyfills.js          | polyfills     | 318.00 kB | \nstyles.css, styles.js | styles        | 210.08 kB | \nmain.js               | main          |  49.83 kB | \nruntime.js            | runtime       |   6.51 kB | \n\n                      | Initial Total |   2.69 MB\n\nBuild at: 2023-03-24T23:10:11.568Z - Hash: da54282da5c1aa00 - Time: 5577ms\n\n** Angular Live Development Server is listening on localhost:4200, open your \nbrowser on http://localhost:4200/ **\n```\n\nIf we inspect the app root component, called `app.component.ts`, we will see a decorator\ncalled `@Component` that specifies the `app-root` that is declared in the `index.html` file.\n\nFiles with the `ts` prefix or suffix utilize TypeScript:  \nhttps://www.typescriptlang.org/  \n\n\n# Configuring our Angular SPA\n\nThe `OnInit` interface is described as:\n\u003e A lifecycle hook that is called after Angular has initialized all data-bound properties of \n\u003e a directive. Define an ngOnInit() method to handle any additional initialization tasks.\n\nOur `AppComponent` within the `app.module.ts` TypeScript file implements this interface.\n\nWe'll need two Terminal instances; one to run our `client` app, and one to run our `API`.\nUnder the Terminal tab, you can click the \"Plus (+)\" button to create another instance,\nand then they'll be shown in the right sidebar.  \n\nIn one Terminal, `cd API` then `dotnet watch run`. In the other Terminal, `cd client` then\n`ng serve`. Now both services will be running.  \n\nEarly on, if we run our application with:\n```js\nngOnInit(): void {\n  this.http.get('https://localhost:5001/api/users').subscribe({\n    next: response =\u003e this.users = response,\n    error: error =\u003e console.log(error),\n    complete: () =\u003e console.log('Request has completed')\n  })\n}\n```\n\nOpen up the DevTools Window by right-clicking in the Browser and clicking on \"Inspect\", \nand we will get a CORS policy error:  \nhttps://developer.mozilla.org/en-US/docs/Web/HTTP/CORS\n\n```\nAccess to XMLHttpRequest at 'https://localhost:5001/api/users' from origin \n'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' \nheader is present on the requested resource.\n```\n\n\u003e Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server \n\u003e to indicate any origins (domain, scheme, or port) other than its own from which a browser should \n\u003e permit loading resources. CORS also relies on a mechanism by which browsers make a \"preflight\" \n\u003e request to the server hosting the cross-origin resource, in order to check that the server will \n\u003e permit the actual request. In that preflight, the browser sends headers that indicate the HTTP \n\u003e method and headers that will be used in the actual request.\n\nFor development purposes, we can fix this by adding this CORS builder to the `Program.cs`:\n```csharp\napp.UseCors(builder =\u003e builder\n    .AllowAnyHeader()\n    .AllowAnyMethod()\n    .WithOrigins(\"http://localhost:4200\"));\n```\n\nOnce this change goes live, we should now see this Header present in our API response:\n```\nAccess-Control-Allow-Origin: http://localhost:4200\n```\nWhich will allow all responses from the 4200 server access port number.\n\n\n## Angular NG Directives\n\nThe documentation for `*ngFor` reads:\n\u003e A structural directive that renders a template for each item in a collection. The directive \n\u003e is placed on an element, which becomes the parent of the cloned templates.\n\u003e \n\u003e The `ngForOf` directive is generally used in the shorthand form `*ngFor`. In this form, the \n\u003e template to be rendered for each iteration is the content of an anchor element containing \n\u003e the directive.\n\n\n## Angular Schematics\n\nA schematic is a template-based code generator that supports complex logic. It is a set \nof instructions for transforming a software project by generating or modifying code. \nSchematics are packaged into collections and installed with npm.\n\nSchematics are part of the Angular ecosystem. The Angular CLI uses schematics to apply \ntransforms to a web-app project. You can modify these schematics, and define new ones \nto do things like update your code to fix breaking changes in a dependency, for example, \nor to add a new configuration option or framework to an existing project.\n\nSchematics that are included in the `@schematics/angular` collection are run by default \nby the commands `ng generate` and `ng add`.  \n\nhttps://angular.io/guide/schematics  \n\n\n## Installing NGX Bootstrap\n\nInstallation instructions are here:\nhttps://valor-software.com/ngx-bootstrap/#/documentation#installation  \n\nBut at the time of this writing, if we try to run:\n```\nng add ngx-bootstrap\n```\n\nIt will fail with an error (since ngx-bootstrap version 9.0.0 is not compatible with\nAngular CLI version 14). So instead, let's run these commands (make sure you are in the\n`client/` directory) with versions explicitly declared:\n```\nnpm install ngx-bootstrap@9\n\nnpm install bootstrap@5\n```\n\nAnd let's install a font package to make our UI classy:\n```\nnpm install font-awesome\n```\n\nHere's a complete list of the Icons available in version `4.7.0` of Font Awesome:  \nhttps://fontawesome.com/v4/icons/  \n\nAnd let's also install NGX Spinner:\n```\nnpm install ngx-spinner@14.0.0 --save\n```\n\nSee also:  \nhttps://github.com/Napster2210/ngx-spinner  \nhttps://napster2210.github.io/ngx-spinner/  \n\n**June 22, 2023 Update**: I ran `npm audit fix` which updated a few of the dependencies\nthat were reported with vulnerabilities and changed the contents of `package-lock.json`.\n\n\n## Using HTTPS in Angular (MacOS)\n\nInside our `client/ssl/` folder, there are two files:\n```\nserver.crt\nserver.key\n```\n\nDouble-click on the `server.crt` file to install it as a `localhost` certificate in the\nMacOS Keychain Access, under the \"login\" option in the left sidebar (it won't show up\nif you have \"System\" or \"System Roots\" selected).  \n\nThese are the detailed installation instructions:\n1. Double click on the certificate (`server.crt`)\n2. Select your desired keychain (**login** should suffice)\n3. Add the certificate\n4. Open **Keychain Access** if it isn't already open\n5. Select the keychain you chose earlier\n6. You should see the certificate **localhost**\n7. Double click on the certificate\n8. Expand **Trust**\n9. Select the option **Always Trust** in **When using this certificate**\n10. Close the certificate window\n\nThe certificate should now be installed. This certificate was generated using this \n`openssl-custom.cnf` configuration:\n```\n[req]\ndefault_bits = 2048\nprompt = no\ndefault_md = sha256\nx509_extensions = v3_req\ndistinguished_name = dn\n\n[dn]\nC = US\nST = KS\nL = Olathe\nO = IT\nOU = IT Department\nemailAddress = webmaster@example.com\nCN = localhost\n\n[v3_req]\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = *.localhost\nDNS.2 = localhost\n```\n\nWith Shell command:\n```shell\n#!bin/bash\n\nopenssl req \\\n    -newkey rsa:2048 \\\n    -x509 \\\n    -nodes \\\n    -keyout server.key \\\n    -new \\\n    -out server.crt \\\n    -config ./openssl-custom.cnf \\\n    -sha256 \\\n    -days 7300\n```\n\n\n# Authentication Basics\n\n## Authentication: Learning Goals\n\nImplement basic authentication in our app and have an understanding of:\n1. How to store passwords in the Database\n2. Using inheritance in C# - DRY (Don't Repeat Yourself)\n3. Using the C# debugger\n4. Using Data Transfer Objects (DTOs)\n5. Validation\n6. JSON Web Tokens (JWTs)\n7. Using services in C#\n8. Middleware\n9. Extension methods - DRY\n\n\n## Where do I start?\n\nRequirements:\n- Users should be able to log in\n- Users should be able to register\n- Users should be able to view other users\n- Users should be able to privately message other users\n\nWe're going to Hash and Salt user passwords to demonstrate the basic concepts of authentication.\nThis isn't a battle-hardened, bullet-proof solution; this is just one step above adding \ncompletely fake authentication. It just gives us the concept of how authentication works in \na very visible way that we can code ourselves.  \n\nRead more about security vulnerabilities, attack vectors, brute force attacks, access controls, \nrainbow tables and other software security concepts here:  \nhttps://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html  \nhttps://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/  \nhttps://attack.mitre.org/techniques/enterprise/  \nhttps://security.stackexchange.com/questions/35523/is-salting-a-hash-really-as-secure-as-common-knowledge-implies  \nhttps://www.authgear.com/post/password-hashing-salting  \n\n\n## Authentication FAQs\n\nSafe storage of passwords:\n\n1. Why don't we use ASP.NET Identity?\n2. Why are we storing the **Password Salt** in the Database? Isn't this less secure?\n\nDon't worry! Later on we will refactor to the widely used and battle-hardened ASP.NET\nCore Identity: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity\n\n\n## API Controller Basics\n\nBy default, an API function written like this:\n```cs\npublic async Task\u003cActionResult\u003cAppUser\u003e\u003e Register(string username, string password)\n```\n\nDerives the parameters from the URL Query Params, like `?username=foo\u0026password=bar`.\n\nIf we wanted to use query params, we would need to specify the attribute `[FromBody]`. \nHowever, we're going to use the power of the `[ApiController]` attribute, and use a DTO \ncalled `RegisterDto` instead.\n\n\n### JSON Web Tokens (JWT)\n\nIndustry standard for tokens (RFC 7519). They are self-contained and can contain:\n- Credentials\n- Claims\n- Other information\n\nBenefits of JWT:\n- No session to manage; JWTs are self-contained tokens\n- Portable; A single token can be used with multiple backends\n- No Cookies are required; Mobile friendly\n- Performance; Once a token is issued, there is no need to make a database request\n  to verify a user's authentication\n\nIn its compact form, JSON Web Tokens consist of three parts separated by dots (`.`), \nwhich are:\n- Header\n- Payload\n- Verify Signature\n\nTherefore, a JWT typically looks like the following:\n```\nxxxxx.yyyyy.zzzzz\n```\n\nHeader:\n```\n{\n  \"alg\": \"HS256\",\n  \"typ\": \"JWT\"\n}\n```\n\nPayload:\n```\n{\n  \"sub\": \"1234567890\",\n  \"name\": \"John Doe\",\n  \"admin\": true\n}\n```\n\nSignature:\n```\nHMACSHA256(\n  base64UrlEncode(header) + \".\" +\n  base64UrlEncode(payload),\n  secret)\n```\n\nRead more here: https://jwt.io/introduction  \n\n\n### Debugger Basics\n\nTo add a debugger breakpoint, just click to the left of the line number you want\nexecution to break (stop). This will create a small Red Dot in the IDE Editor window.  \n\nTo run and debug our API, click on the **Run and Debug** tool window (SHIFT + CMD + D),\nand then click on the Play button next to **.NET Core Launch (web)** located in the \ntop-left corner of the IDE.  \n\nYou can also Attach the debugger to an already-running API instance (note: this will\nnot work when using Hot Reload). The list of \"debuggable processes\" is often quite \nlarge, so you can search the list for \"API\" and then click on the appropriate Process ID.  \n\nIn Windows environments the debuggable process might be named `API.exe`.\n\n\n### Development JWT TokenKey\n\nIn the `appsettings.Development.json` local configuration file, add this property:\n```js\n\"TokenKey\": \"super secret unguessable key\"\n```\n\nWhen we call our new and improved Login API, a successful login response will have a \n`\"token\"` property that looks like this:\n```js\n{\n    \"username\": \"jim\",\n    \"token\": \"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJqaW0iLCJuYmYiOjE2ODAyOTgwOTksImV4cCI6MTY4MDkwMjg5OSwiaWF0IjoxNjgwMjk4MDk5fQ.1qccJUbwCaZUpX2LoB_e2QuikREfpmJc0bWFmfQ61PGBSfYQVegc_o-RVVV4ig3-7QV9AbHX9l3skvbDmO_ckg\"\n}\n```\n\nIf we go to https://jwt.ms/ and paste the token in there, we will see a Decoded Token\nlike:\n```\n{\n  \"alg\": \"HS512\",\n  \"typ\": \"JWT\"\n}.{\n  \"nameid\": \"jim\",\n  \"nbf\": 1680298099,\n  \"exp\": 1680902899,\n  \"iat\": 1680298099\n}.[Signature]\n```\n\n\n# Client Login and Register\n\n## Client Login and Register: Learning Goals\n\nImplement the login and register functionality into the apps as well as understanding:\n1. Creating components using the Angular CLI\n2. Using Angular Template forms\n3. Using Angular services\n4. Understanding Observables\n5. Using Angular structural directives to conditionally display elements on a page\n6. Component communication from parent to child\n7. Component communication from child to parent\n\n\n## Creating a Nav Bar\n\nBootstrap 5.2 Examples: https://getbootstrap.com/docs/5.2/examples/  \n\n```html\n\u003cnav class=\"navbar navbar-expand-md navbar-dark fixed-top bg-dark\"\u003e\n  \u003cdiv class=\"container-fluid\"\u003e\n    \u003ca class=\"navbar-brand\" href=\"#\"\u003eCarousel\u003c/a\u003e\n    \u003cbutton class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarCollapse\" aria-controls=\"navbarCollapse\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"\u003e\n      \u003cspan class=\"navbar-toggler-icon\"\u003e\u003c/span\u003e\n    \u003c/button\u003e\n    \u003cdiv class=\"collapse navbar-collapse\" id=\"navbarCollapse\"\u003e\n      \u003cul class=\"navbar-nav me-auto mb-2 mb-md-0\"\u003e\n        \u003cli class=\"nav-item\"\u003e\n          \u003ca class=\"nav-link active\" aria-current=\"page\" href=\"#\"\u003eHome\u003c/a\u003e\n        \u003c/li\u003e\n        \u003cli class=\"nav-item\"\u003e\n          \u003ca class=\"nav-link\" href=\"#\"\u003eLink\u003c/a\u003e\n        \u003c/li\u003e\n        \u003cli class=\"nav-item\"\u003e\n          \u003ca class=\"nav-link disabled\"\u003eDisabled\u003c/a\u003e\n        \u003c/li\u003e\n      \u003c/ul\u003e\n      \u003cform class=\"d-flex\" role=\"search\"\u003e\n        \u003cinput class=\"form-control me-2\" type=\"search\" placeholder=\"Search\" aria-label=\"Search\"\u003e\n        \u003cbutton class=\"btn btn-outline-success\" type=\"submit\"\u003eSearch\u003c/button\u003e\n      \u003c/form\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/nav\u003e\n```\n\n```shell\n~/GitProjects/UdemyDatingApp/client % ng g --help\n\n~/GitProjects/UdemyDatingApp/client % ng g component nav --dry-run\nCREATE src/app/nav/nav.component.css (0 bytes)\nCREATE src/app/nav/nav.component.html (18 bytes)\nCREATE src/app/nav/nav.component.spec.ts (578 bytes)\nCREATE src/app/nav/nav.component.ts (263 bytes)\nUPDATE src/app/app.module.ts (541 bytes)\n```\n\nRun this command to create the Nav Bar component:\n```\nng g component nav --skip-tests\n```\n\nNGX Bootstrap Dropdown component:  \nhttps://valor-software.com/ngx-bootstrap/#/components/dropdowns?tab=overview\n\n\n## What are Observables?\n\nNew standard for managing async data included in ES7 (ECMAScript 2016):  \nhttps://www.w3schools.io/javascript/es7-features-introduction/  \nhttps://github.com/tc39/proposals  \n\nObservables were introduced in Angular v2, and everything that is asynchronous in Angular \nuses Observables. For browser backwards compatibility, there is a compiler called Babel \nthat can assist.  \nhttps://babeljs.io/  \n\n`Observables` are lazy collections of multiple values over time. We can use them to stream\ndata. We typically use them for HTTP requests and when we want our `Components` to observe \nvalues set up inside a `Service`.  \n\nYou can think of observables like a newsletter:\n- Only subscribers of the newsletter receive the newsletter\n- If no-one subscribes to the newsletter, it probably will not be printed\n\n\n### Promises vs. Observables\n\nPromises...\n1. Provide a single future value\n2. Are not lazy\n3. Can not cancel\n\nObservables...\n1. Emit multiple values over time\n2. Are lazy (they won't do anything until somebody subscribes to them)\n3. Are able to be canceled\n4. Can be used with map, filter, reduce and other operators\n\nWe can also use RxJS (Reactive Extensions JavaScript) with Observables!  \nhttps://rxjs.dev/\n\n\n# Section 6: Routing in Angular\n\n## Routing in Angular: Learning Goals\n\nImplement routing in our Angular app and have an understanding of:\n1. Angular routing (Single Page Application)\n2. Adding a bootstrap theme\n3. Using Angular route guards (they don't provide good security, but prevent\n   users from navigating to areas they're not supposed to).\n4. Using a Shared Module\n\n```html\n\u003ca class=\"nav-link\" routerLink=\"/members\" routerLinkActive=\"active\"\u003eMatches\u003c/a\u003e\n\u003ca class=\"nav-link\" routerLink=\"/lists\" routerLinkActive=\"active\"\u003eLists\u003c/a\u003e\n\u003ca class=\"nav-link\" routerLink=\"/messages\" routerLinkActive=\"active\"\u003eMessages\u003c/a\u003e\n```\n\nhttps://angular.io/guide/routing-overview  \n\n\n## Installing Ngx Toastr\n\nhttps://github.com/scttcper/ngx-toastr  \n\nhttps://ngx-toastr.vercel.app/  \n\n```\ncd client/\nnpm install --cache /tmp/empty-cache\nnpm install\nnpm install ngx-toastr@14.3.0 --save\n```\n\nhttps://bootswatch.com/  \n\n```\nnpm install bootswatch\n```\n\n\n# Section 7: Error Handling\n\n## Error Handling: Learning Goals\n\nImplement global error handling in both the API and the Angular application.\nAlso to have an understanding of:\n1. API Middleware\n2. Angular Interceptors\n3. Troubleshooting Exceptions - \"Error Handling Utopia\"\n\n\n# Section 8: Extending the API\n\n## Extending the API: Learning Goals\n\nImplement further functionality into our API and gain an understanding of:\n1. Entity Framework Relationships\n2. Entity Framework Conventions\n3. Seeding Data into the Database\n4. The Repository Pattern\n5. Using AutoMapper\n\n\n## The Repository Pattern\n\n\u003e A Repository mediates between the domain and data mapping layers, acting\n\u003e like an in-memory domain object collection.\n\u003e - Martin Fowler (Patterns of Enterprise Architecture)\n\nWe've got a web server, the Kestrel server, in our API \nhttps://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-7.0 \nand requests come in to our controller endpoint. In our controllers we inject\nthe DB context, and our DB context represents a session with our database.\n\nIn our case, the repository is an abstraction from `DbContext`. The pattern\nwill encapsulate the logic (ex: `GetUser(), GetUsers(), UpdateUser()`), instead\nof exposing the hundreds of methods native to `DbContext`. It also reduces\nduplicate query logic, across multiple controllers. The repository pattern\nalso promotes testability - the repository is easier to test against than it\nis to test the `DbContext` (ex: `IRepository, MockRepository`). The pattern\nalso decouples our application from the persistence framework (which in a way,\nEntity Framework already accomplishes that to some extent).\n\n\n# Section 9: Building the User Interface\n\n## Building the User Interface: Learning Goals\n\nImplement the components that make up the user interface in our client \napplication and gain an understanding of:\n1. Using Typescript types\n2. Using the async pipe\n3. Using bootstrap for styling\n4. Basic CSS tricks to enhance the look\n5. Using a 3rd party photo gallery\n\n\n## Installing Ngx Gallery\n\nhttps://github.com/kolkov/ngx-gallery\n\n```\ncd client/\nnpm install @kolkov/ngx-gallery --legacy-peer-deps\nnpm audit fix\n```\n\n\n# Section 10: Updating Resources\n\n## Updating Resources: Learning Goals\n\nImplement persistence when updating resources in the API and gaining an\nunderstanding of:\n1. Angular Template forms\n2. The `CanDeactivate` Route Guard\n3. The `@ViewChild` decorator\n4. Persisting changes to the API\n5. Adding loading indicators to the client app\n6. Caching data in Angular services\n\n\n# Section 11: Adding Photo Upload Functionality\n\n## Adding Photo Upload Functionality: Learning Goals\n\nImplement photo upload functionality in the application and gain an\nunderstanding of the following:\n1. Photo storage options\n2. Adding related entities\n3. Using a 3rd party API\n4. Using the Debugger (again!)\n5. Updating and deleting resources\n6. What to return when creating resources in a REST based API\n\nPhoto Storage with Cloudinary:  \nhttps://cloudinary.com/\n\n## Image Upload Arch Flow\n\n1. Client uploads photo to API with JTW token\n2. Server uploads the photo to Cloudinary\n3. Cloudinary stores photo and sends response\n4. API saves photo URL and public ID to DB\n5. Saved in DB and given auto-generated ID\n6. 201 Created Response sent to client with location header\n\n### NuGet: Installing CloudinaryDotNet\n\nOpen the **Command Palette** (SHIFT + CMD + P), and search for \"NuGet\" and click on the\noption that reads \"NuGet: Open NuGet Gallery\".\n\nLook for \"cloudinary\", and install \"CloudinaryDotNet\" by Cloudinary.\nI selected version `1.20.0`.\n\nAdditionally, I added `API/appsettings.json` to the project `.gitignore` file.\nThis is where we will store the Cloudinary secret API Keys.\nThis is the original contents of the file for reference:\n```json\n{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\"\n}\n```\n\n## Installing Ng2 File Upload\n\nhttps://github.com/valor-software/ng2-file-upload  \n\n```\ncd client/\nnpm install ng2-file-upload@2.0.0-3 --legacy-peer-deps\n```\n\nNOTE: Using `npm install ng2-file-upload@next --legacy-peer-deps` will result\nin these compiler errors:\n```\nError: node_modules/ng2-file-upload/file-upload/file-drop.directive.d.ts:19:18 - error TS2707: Generic type 'DirectiveDeclaration' requires between 6 and 8 type arguments.\n\n19     static ɵdir: i0.DirectiveDeclaration\u003cFileDropDirective, \"[ng2FileDrop]\", never, { \"uploader\": \"uploader\"; }, { \"fileOver\": \"fileOver\"; \"onFileDrop\": \"onFileDrop\"; }, never, never, false, never\u003e;\n                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\nError: node_modules/ng2-file-upload/file-upload/file-select.directive.d.ts:14:18 - error TS2707: Generic type 'DirectiveDeclaration' requires between 6 and 8 type arguments.\n\n14     static ɵdir: i0.DirectiveDeclaration\u003cFileSelectDirective, \"[ng2FileSelect]\", never, { \"uploader\": \"uploader\"; }, { \"onFileSelected\": \"onFileSelected\"; }, never, never, false, never\u003e;\n                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\n** Angular Live Development Server is listening on localhost:4200, open your browser on https://localhost:4200/ **\n\n\n✖ Failed to compile.\n```\n\n\n# Section 12: Reactive Forms\n\n## Reactive Forms: Learning Goals\n\nImplement more advanced forms using Reactive Forms in Angular and understand\nhow to:\n1. Use Reactive Forms\n2. Use Angular Validation for inputs\n3. Implement custom validators\n4. Implement reusable form controls\n5. Working with Date inputs\n\n\n# Section 13: Paging, Sorting and Filtering\n\n## Paging, Sorting and Filtering: Learning Goals\n\nImplement paging, sorting, and filtering, and gain an understanding of the\nfollowing: \n1. How to implement pagination on the API \u0026 client\n2. Deferred Execution using IQueryable\n3. How to implement filtering on the API \u0026 client\n4. How to implement sorting on the API \u0026 client\n5. Using Action Filters\n6. Adding a TimeAgo pipe\n7. Implement caching in the client for paginated resources\n\n### Pagination: Why?\n- Helps avoid performance problems.\n- Parameters are passed by query string:\n  - https://localhost:5001/api/users?pageNumber=1\u0026pageSize=5\n- Page size should be limited.\n- We should always paginate the results.\n\n### Deferred Execution: What is it?\nWe build up an expression tree and we store this as an `IQueryable` of type\nwhatever:\n```csharp\nIQueryable\u003cUser\u003e --\u003e var query = context.Users\n  .Where(x =\u003e x.Gender == gender)\n  .OrderBy(x =\u003e x.UserName)\n  .Take(5)\n  .Skip(5)\n  .AsQueryable();\n```\n\nThe query only gets **executed** when we use a `ToListAsync()`:\n```csharp\nquery.ToListAsync()\nquery.ToArrayAsync()\nquery.ToDictionary()\n\nquery.Count()\n```\n\n## Installing Ngx TimeAgo\n\nhttps://ihym.github.io/ngx-timeago/  \n\nhttps://www.npmjs.com/package/ngx-timeago  \n\n```\ncd client/\nnpm show ngx-timeago versions\nnpm install ngx-timeago@2.0.0 --save\n```\n\n\n# Section 14: Adding the Like User feature\n\n## Adding the Like User feature: Learning Goals\n\nImplement the 'Like User' functionality and an understanding of the following: \n1. Many to many relationship\n2. Configuring entities in the DbContext:\n   An `AppUser` can be liked by many `AppUsers` (and vice versa)\n3. Implement a solution using the Fluent API\n\n\n# Section 15: Adding the 'Messaging' feature\n\n## Messages Feature: Learning Goals\n\nImplement the Messaging functionality and gain an understanding of the following:\n1. More many-to-many relationships\n2. Using query params in Angular\n3. Using Route resolvers in Angular\n\n\n# Section 16: Identity and Role Management\n\n## Identity and Role Management: Learning Goals\n\nRefactor our code to use ASP.NET Identity and gain an understanding of the following:\n1. Using .Net Identity\n2. Role management\n3. Policy based authorization\n4. UserManager\u003cT\u003e\n5. SignInManager\u003cT\u003e\n6. RoleManager\u003cT\u003e\n\n### Why change our code?\n- Identity and role management is battle-hardened, written and tested by Microsoft.\n- Comes with a password hasher with 10,000 salt iterations by default.\n- Full framework for managing members and roles.\n- Provides an Entity Framework schema to create the needed tables.\n- Highly customizable.\n\nNote that ASP.NET Identity is different from **Identity Server**.\n\n\n# Section 17: Adding support for SignalR\n\n## SignalR: Learning Goals\n\nImplement SignalR into our application and understand how to:\n1. Use and set up SignalR on both the API and the client\n2. Implement online presence\n3. Implement live chat between users\n\n### What is SignalR?\n- Open source library that provides real-time web functionality to apps\n- Good for Dashboards and Monitoring apps\n- Good for Collaborative apps (like whiteboards)\n- Good for apps that require notifications and chat apps\n- Handles connection (and re-connects) management automatically\n- Sends messages to all connected clients simultaneously\n- Sends messages to specific client groups of clients\n- Supports WebSockets, Server-Sent events, and Long polling\n- Offers a client side npm package\n\n## Installing Microsoft SignalR\n\nhttps://www.npmjs.com/package/@microsoft/signalr\n\n```\ncd client/\nnpm install @microsoft/signalr\nnpm audit fix\n```\n\n\n# Section 18: Unit of work pattern and finishing touches\n\n## Unit of work pattern and finishing touches: Learning Goals\n\nImplement the Unit of work pattern and gain an understanding of the following:\n1. The Unit of Work pattern\n2. Optimizing queries to the DB\n3. Adding a confirm dialog service\n4. Finishing touches\n\n### What is Unit of Work?\nDescribed as: Maintains a list of objects affected by a business transaction\nand coordinates the writing of changes.\n\nRight now, we have Controllers and Repositories, and we use `SaveChanges()`.\nThe repositories are injected into the Controller, and each one needs its\nown instance of the `DataContext` as well. This could result in data\ninconsistency, if one `SaveChanges()` works, and the other does not.\n\nThe Unit of Work injects the `DataContext` and passes that down as a parameter\nto the different Repositories.\n\n\n# Bonus: Photo Management Challenge\n\n## Requirements\n1. Any photos a user uploads should be un-approved.\n2. Only admins or moderators can approve photos.\n3. No other user should be able to see unapproved photos.\n4. The user that uploaded the photo should be able to see the photo,\n   but it should be clearly identified as \"awaiting approval\".\n5. When a user uploads their first photo, this should not be set as their\n   \"Main Photo\" (because it must first be approved).\n6. When an Admin or a Moderator approves a photo for a user that does not\n   have a Main Photo, then this action should set the photo to their Main.\n\n## Guidance\n1. Add `isApproved` to the Photo entity.\n2. Add a `DbSet` for the Photos so we can query directly.\n3. Update the `PhotoDto`.\n4. Update the `Seed.cs` Users so that the initial photo is approved for\n   seeded users.\n5. Drop the database and add a new migration.\n6. Add a Query filter to only return approved photos.\n7. Ignore the Query filter for the current user (`GetMemberAsync`) so the\n   current user still sees their unapproved photos.\n8. Add a `PhotoForApprovalDto` with the Photo ID, the URL, the Username\n   and the `isApproved` status.\n\n... Further details are included in the course PDF.\n\n\n# Section 19: Publishing\n\n## Publishing: Learning Goals\n\nActually publish the app and gain an understanding of how to:\n1. Prepare the app for publication\n2. What to consider before publishing\n3. Switching DBs\n4. Serving static content from the API server.\n5. Publishing to Heroku (Free!) using PostgreSQL\n6. Integrating Heroku to GitHub\n7. Using git branches\n\n### What to consider when publishing\n1. Environment variables - Cloudinary settings, Token key, etc.\n2. Localhost\n3. CORS (Cross-Origin Resource Sharing) - if hosting the client app in\n   different domain\n4. Database - goodbye SQLite!\n5. Cost - what is the budget for this?\n6. Capacity / scalability\n7. Seed data - admin and moderator users?\n8. Fake API delays! (these need to be removed)\n\n\n### Docker + Fly.io Screenshots\n\n| Docker Dashboard |\n| :---: |\n| ![Docker Dashboard](Screenshots/app-12.png) |\n\n| Docker GitHub CI Action |\n| :---: |\n| ![Docker GitHub CI Action](Screenshots/app-13.png) |\n\n| Fly.io Web Dashboard |\n| :---: |\n| ![Fly.io Web Dashboard](Screenshots/app-14.png) |\n\n### Publishing preparation\nUpdate `client/angular.json` \"outputPath\" to: `../API/wwwroot`.  \nRun:\n```\nng build\n```\nUpdate \"budgets\" to:\n```\n\"maximumWarning\": \"1mb\"\n\"maximumError\": \"2mb\"\n```\n\n### Docker download\nLink to Docker for Desktop:  \nhttps://www.docker.com/products/docker-desktop/\n\n### PostgreSQL\nPowerful, open source object-relational database system:  \nhttps://www.postgresql.org/\n\n## Migrating Entity Framework from SQLite to PostgreSQL\nRun:\n```\ndocker run --name postgres -e POSTGRES_PASSWORD=postgrespw -p 5432:5432 -d postgres:latest\n```\n\nIn VS Code, go to the Extensions tab (looks like 4 Tetris blocks), and search\nfor \"Postgres\".  \n\nInstall the **PostgreSQL** by Chris Kolkman extension.  \n\nThen, open the **Command Palette** (SHIFT + CMD + P), and search for \"NuGet\"\nand click on the option that reads \"NuGet: Open NuGet Gallery\".\n\nLook for \"Npgsql.EntityFrameworkCore.PostgreSQL\" by Shay Rojansky, Austin Drenski,\nYoh Deadfall, and check the `API.csproj` checkbox, and install the **7.0.0**\nversion.\n\nThe `API.csproj` file should now have a new entry like this:\n```xml\n\u003cItemGroup\u003e\n  \u003cPackageReference Include=\"Npgsql.EntityFrameworkCore.PostgreSQL\" Version=\"7.0.0\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\nThen drop the SQLite database by running:\n```\ncd API/\ndotnet ef database drop\n```\n\nThen update our `appsettings.Development.json` file from:\n```\n\"DefaultConnection\": \"Data source=datingapp.db\"\n```\nto:\n```\n\"DefaultConnection\": \"Server=localhost; Port=5432; User Id=postgres; Password=postgrespw; Database=datingapp\"\n```\n\nThen delete the `Migrations/` folder (inside of `API/Data/`), then run:\n```\ndotnet ef migrations add PostgresInitial -o Data/Migrations\n```\n\nIf we run `dotnet run`, we'll get an error like this:\n```\n at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlRawAsync(DatabaseFacade databaseFacade, String sql, IEnumerable`1 parameters, CancellationToken cancellationToken)\n at Program.\u003cMain\u003e$(String[] args) in /Users/schanzke/GitProjects/UdemyDatingApp/API/Program.cs:line 56\nException data:\n  Severity: ERROR\n  SqlState: 42601\n  MessageText: syntax error at or near \"[\"\n  Position: 13\n  File: scan.l\n  Line: 1241\n  Routine: scanner_yyerror\n```\n\nBecause PostgreSQL does not support the `[Connections]` SQLite syntax (this is\nfixed as part of this commit).\n\nWe'll also get this error (this is addressed by doing UTC conversions in\nthe `Seed.cs` class):\n```\nfail: Program[0]\n      An error occurred during migration\n      Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while \n      saving the entity changes. See the inner exception for details.\n       ---\u003e System.InvalidCastException: Cannot write DateTime with \n       Kind=Unspecified to PostgreSQL type 'timestamp with time zone', \n       only UTC is supported. Note that it's not possible to mix DateTimes \n       with different Kinds in an array/range. See the \n       Npgsql.EnableLegacyTimestampBehavior AppContext switch to enable legacy \n       behavior.\n```\n\n## Dockerizing our app to deploy to fly.io\nGo to the Extensions tab (looks like 4 Tetris blocks), and search\nfor \"Docker\". Install the **Docker** by Microsoft extension.  \n\nThese entries need to be added to the git-ignored `appsettings.json` file:\n```json\n\"ConnectionStrings\": {\n  \"DefaultConnection\": \"Server=host.docker.internal; Port=5432; User Id=postgres; Password=postgrespw; Database=datingapp\"\n},\n\"TokenKey\": \"super secret unguessable key\"\n```\n\nBuild the Docker image by running this from our `API/` folder:\n```\ncd API/\ndocker build -t \u003cdocker_username\u003e/datingapp .\n```\n\nTo run the Docker image:\n```\ndocker run --rm -it -p 8080:80 docker.io/\u003cdocker_username\u003e/datingapp:latest\n```\n\nAnd then in a browser, go to: http://localhost:8080/  \n\nThe Docker image can be pushed to docker.io with:\n```\ndocker push \u003cdocker_username\u003e/datingapp:latest\n```\n\nIf you have trouble authenticating, you can use `docker login`.\n\n## Deploy to fly.io\nFly is a platform for running full stack apps and databases close to your users. \nWe’ve been hammering on this thing since 2017, and we think it’s pretty great.\n\nSee website: https://fly.io/  \n\nInstall the fly.io command line tools:\n```\nbrew install flyctl\n```\n\nThen authenticate:\n```\nflyctl auth signup\n```\n\nLaunch our Docker image to fly.io using:\n```\nfly launch --image docker-username/datingapp:latest\n```\n\nThe configuration should look something like:\n```\nUsing image docker-username/datingapp:latest\nCreating app in /Users/username/GitProjects/UdemyDatingApp\nWe're about to launch your app on Fly.io. Here's what you're getting:\n\nOrganization: Full Name                (fly launch defaults to the personal org)\nName:         udemydatingapp           (derived from your directory name)\nRegion:       Ashburn, Virginia (US)   (this is the fastest region for you)\nApp Machines: shared-cpu-1x, 256MB RAM (most apps need about 1GB of RAM)\nPostgres:     Fly Postgres\nRedis:        \u003cnone\u003e                   (not requested)\n\nHostname: https://udemydatingapp-db.fly.dev/\n```\n\nNote: For this Udemy course, I was blocked from deployment with a 403: Forbidden\nerror (probably ZScaler) like:\n```\nFailed attaching udemydatingapp to the Postgres cluster udemydatingapp-db: \n  can't build tunnel for personal: websocket: failed to WebSocket dial: \n  expected handshake response status code 101 but got 403.\n  Try attaching manually with 'fly postgres attach --app udemydatingapp udemydatingapp-db'\nError: can't build tunnel for personal: websocket: failed to WebSocket dial: \n  expected handshake response status code 101 but got 403\nError: failed to fetch an image or build from source: image must be amd64 \n  architecture for linux os, found arm64 linux\n```\n\nI had to run this to fix the issue:\n```\nfly wireguard websockets disable\nfly wireguard reset\nfly postgres attach --app udemydatingapp udemydatingapp-db\n```\n\nSet the Cloudinary API Secret Key:\n```\nfly secrets set CloudinarySettings__ApiSecret=secret_apikey06PgY\n```\n\nGenerate a strong password using a tool like:\nhttps://delinea.com/resources/password-generator-it-tool\n\nAnd set the Token Key:\n```\nfly secrets set TokenKey=somestrongpasswordycPTHXIwZfpbJI\n```\n\nList the fly secrets by running `fly secrets list`, and see:\n```\nNAME                            DIGEST                  CREATED AT \nCloudinarySettings__ApiSecret   708c5f0c74b2f804        10m25s ago\nDATABASE_URL                    b82821dc05bcc987        9s ago    \nTokenKey                        fc461f13437f184b        6m56s ago \n```\n\nTest the connection like:\n```\nfly ping udemydatingapp-db.internal\n```\n\n## Create the config files for fly.io\nAfter updating the `ApplicationServiceExtensions.cs` and `Program.cs` we need\nto rebuild and push an update Docker image:\n```\ncd API/\ndocker build -t \u003cdocker_username\u003e/datingapp .\ndocker push \u003cdocker_username\u003e/datingapp:latest\n```\n\nAnd deploy our app to fly.io:\n```\nfly deploy\n```\n\nUnfortunately we'll run into this error with the Apple Silicon ARM Chip:\n```\nError: failed to fetch an image or build from source: image must be amd64 \n  architecture for linux os, found arm64 linux\n```\n\n## Using github actions\n1. Copy the template from https://github.com/docker/build-push-action\n2. Paste that template into a new `.github/workflows/docker-push.yml` file.\n3. Go to the GitHub repository **Settings \u003e Secrets and variables \u003e Actions**.\n4. Create a 'New Repository Secret' with name: `DOCKERHUB_USERNAME`.\n5. Create a 'New Repository Secret' with name: `DOCKERHUB_TOKEN`.\n   This will require an Access Token to be generated in Docker Hub, under\n   My Account \u003e Security. See: https://docs.docker.com/security/for-developers/access-tokens/\n\nLog into GitHub, go to Actions, select the `docker-push` action, and Run the\nWorkflow. Hopefully it succeeds!\n\nFinally, we can run `fly deploy`. Since I haven't given my credit card, I get \nthe error:\n```\nError: input:3: createRelease We need your payment information to continue!\nAdd a credit card or buy credit: https://fly.io/dashboard/full-name/billing\n```\n\n### Continuous Deployment with Fly.io and GitHub Actions\n\nhttps://fly.io/docs/app-guides/continuous-deployment-with-github-actions/\n\nSome fly.io tips and tricks:\n```\n# Proxies connections to a Fly Machine through a WireGuard tunnel. By default, \n# connects to the first Machine address returned by an internal DNS query on \n# the app.\nfly proxy 6543:5432 -a datingapp-db\n```\n\n```\n# View application logs as generated by the application running on the Fly \n# platform.  Logs can be filtered to a specific instance using the \n# --instance/-i flag or to all instances running in a specific region using \n# the --region/-r flag.\nfly logs -a datingapp\n```\n\n# Publishing to Azure: An Introduction\nFirst of all we want to make sure our app runs without issue on Sql Server 2019. \nFor windows you can just install this directly, but for Mac/Linux then you can \nget a docker image of SQL as Microsoft now has a Linux version of SQL. If you \nare on Windows but do not have SQL installed then so long as you have Docker \nthen you can go ahead and do the same as me.\n\n## Setting up Sql Server for Development\nSince SQL is a bit of a big install I'm going to download the files to my \ncomputer by running the following command:\n```shell\ndocker pull microsoft/mssql-server-linux:latest\n```\n\nSQL Server requires a bit more memory than other DBs so I am also going to \nincrease the memory for Docker in the preferences to 4GB.\n\nThen we can run the following command to run the SQL Server:\n```shell\ndocker run -d --name sqldemo -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Password1!' -p 1433:1433 microsoft/mssql-server-linux\n```\n\nSql SA account needs a strong password, and I am not saying the above is (!) \nbut it does meet the complexity requirements.\n\n## Switching to use SQL Server for dev.\nI'm going to create a new branch so that I do not interfere with the `master` \nbranch. Run the following:\n```shell\ngit checkout -b AzurePublish\n```\n\nAdd the following Sql Server provider via Nuget: **Microsoft.EntityFrameworkCore.SqlServer**  \n\nYou can remove the package for Sqlite and Postgres if you still have them \ninstalled - we only need SqlServer for this Ensure you pick the same version \nas your runtime.\n\nOpen the `appsettings.development.json` and change the default connection \nstring to the following:\n```json\n\"ConnectionStrings\" : {\n  \"DefaultConnection\": \"Server=localhost; User Id=sa; Password=Password1!; Database=datingappdb\"\n}\n```\n\nUpdate the `ApplicationServiceExtensions` to use this:\n```csharp\nservices.AddDbContext\u003cDataContext\u003e(options =\u003e\n{\n    options.UseSqlServer(config.GetConnectionString(\"DefaultConnection\"));\n});\n```\n\nDelete the migrations folder from Data/Migrations and create a new migration \nfor the Sql Server provider:\n```shell\ndotnet ef migrations add SqlInitial -o Data/Migrations\n```\n\nCheck the migration and ensure you can see Sql server specific annotations \nin there:\n```csharp\nmigrationBuilder.CreateTable(\n    name: \"AspNetRoles\",\n    columns: table =\u003e new\n    {\n        Id = table.Column\u003cint\u003e(type: \"int\", nullable: false)\n            .Annotation(\"SqlServer:Identity\", \"1\", \"1\"),\n        Name = table.Column\u003cstring\u003e(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n        NormalizedName = table.Column\u003cstring\u003e(type: \"nvarchar(256)\", maxLength: 256, nullable: true),\n        ConcurrencyStamp = table.Column\u003cstring\u003e(type: \"nvarchar(256)\", nullable: true)\n    },\n```\n\nRestart the app and make sure everything works! ... Well, it doesn’t because \nSql server is special. So we need to add an extra bit of configuration here to \nthe `DataContext.cs` class and make sure one of the `UserLike` specifies no \naction for the delete behaviour:\n```csharp\nbuilder.Entity\u003cUserLike\u003e()\n    .HasOne(s =\u003e s.SourceUser)\n    .WithMany(l =\u003e l.LikedUsers)\n    .HasForeignKey(s =\u003e s.SourceUserId)\n    .OnDelete(DeleteBehavior.NoAction);\nbuilder.Entity\u003cUserLike\u003e()\n    .HasOne(s =\u003e s.LikedUser)\n    .WithMany(l =\u003e l.LikedByUsers)\n    .HasForeignKey(s =\u003e s.LikedUserId)\n    .OnDelete(DeleteBehavior.Cascade);\n```\n\nDelete the migrations folder and recreate the migration:\n```shell\ndotnet ef migrations add SqlInitial -o Data/Migrations\n```\n\nRestart the app again and make sure we have success!\n\nThis time everything goes smoothly. Restart the angular app and ensure we \ncan operate the application without any errors on `localhost 4200`. It should \nwork fine.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhelloimkevo%2Fudemydatingapp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhelloimkevo%2Fudemydatingapp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhelloimkevo%2Fudemydatingapp/lists"}