6 Commits

Author SHA1 Message Date
a51e61aabc Attempting to fix code first migrations not running against a fresh db
All checks were successful
ci/woodpecker/tag/api_build Pipeline was successful
ci/woodpecker/tag/api_uploadimage Pipeline was successful
2024-04-10 20:52:31 -05:00
8dd914c6c2 Committing work fixing issues found while testing on dev1.bookmarks
All checks were successful
ci/woodpecker/tag/api_build Pipeline was successful
ci/woodpecker/tag/api_uploadimage Pipeline was successful
ci/woodpecker/tag/web_uploadimage Pipeline was successful
ci/woodpecker/push/api_build Pipeline was successful
- Modified EnvironmentUtils to fix proper casing for getting ASPNETCore_Environment
- On Web, modify package.json to delete 'homepage': '.', line to fix issue with app not serving up subroutes. See: https://stackoverflow.com/questions/63036631/nginx-returning-404-when-trying-get-subroute-in-dockerized-react-app
- Added docker-compose for easier setup and deployment of local docker containers
2024-04-08 23:12:16 -05:00
d83b13c093 Updated environment variable to be blank by default
All checks were successful
ci/woodpecker/tag/api_build Pipeline was successful
ci/woodpecker/tag/api_uploadimage Pipeline was successful
2024-04-07 20:36:11 -05:00
9fae3b978f Modified ClaimNameAttribute to support multiple claim names depending on environment
All checks were successful
ci/woodpecker/tag/api_build Pipeline was successful
ci/woodpecker/tag/api_uploadimage Pipeline was successful
Environment name will be pulled from environment variable: ASPNETCORE_ENVIRONMENT
2024-04-07 20:18:53 -05:00
822a8379aa Merge pull request 'Created Woodpecker CI/CD deployment' (#38) from feature/CreateCIPlans into develop
All checks were successful
ci/woodpecker/push/api_build Pipeline was successful
Reviewed-on: #38
2024-04-07 15:20:05 -05:00
a5d5ed048f Created Woodpecker CI/CD deployment
- Created Dockerfile for packing up API and Web projects as Docker image
2024-04-07 14:51:28 -05:00
149 changed files with 31067 additions and 18290 deletions

3
.gitignore vendored
View File

@ -247,4 +247,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env
Logs/*
Logs/*
*exclude*

View File

@ -0,0 +1,12 @@
when:
- event: [push, pull_request, tag]
ref: [refs/tags/APIDEV-*, refs/tags/API-*, refs/heads/api/*]
steps:
- name: (YABA.API) Build and run tests
when:
- event: [push, pull_request, tag]
branch: [api/*]
image: mcr.microsoft.com/dotnet/sdk:6.0
commands:
- dotnet build ./API
- dotnet test ./API

View File

@ -0,0 +1,36 @@
when:
- event: tag
ref: [refs/tags/APIDEV-*, refs/tags/API-*]
steps:
- name: (YABA.API) Determining Docker image version number (dev)
when:
- ref: refs/tags/APIDEV-*
image: alpine:latest
commands:
- rm -f tags.txt
- echo ${CI_COMMIT_TAG} | sed -e "s/^APIDEV-//" >> tags.txt
- echo "latest-dev" >> tags.txt
- name: (YABA.API) Determining Docker image version number (prod)
image: alpine:latest
when:
- ref: refs/tags/API-*
commands:
- rm -f tags.txt
- echo ${CI_COMMIT_TAG} | sed -e "s/^API-//" >> tags.txt
- echo "latest" >> tags.txt
- name: (YABA.API) Package and Upload Docker Image
image: woodpeckerci/plugin-docker-buildx
settings:
repo: gitea.iwanaga.moe/cjtibule/yaba/api
context: ./API
dockerfile: ./API/YABA.API/Dockerfile
tags_file: tags.txt
platforms:
- linux/amd64
username:
from_secret: gitea_yaba_registry_username
password:
from_secret: gitea_yaba_registry_password
registry: gitea.iwanaga.moe
depends_on:
- api_build

View File

@ -0,0 +1,52 @@
when:
- event: tag
ref: [refs/tags/WEBDEV-*, refs/tags/WEB-*]
steps:
- name: (YABA.Web) Determining Docker image version number (dev)
when:
- ref: refs/tags/WEBDEV-*
image: alpine:latest
commands:
- rm -f tags.txt
- echo ${CI_COMMIT_TAG} | sed -e "s/^WEBDEV-//" >> tags.txt
- echo "latest-dev" >> tags.txt
- name: (YABA.Web) Determining Docker image version number (prod)
when:
- ref: refs/tags/WEB-*
image: alpine:latest
commands:
- rm -f tags.txt
- echo ${CI_COMMIT_TAG} | sed -e "s/^WEB-//" >> tags.txt
- echo "latest" >> tags.txt
- name: (YABA.Web) Package and Upload Docker Image (dev)
when:
- ref: refs/tags/WEBDEV-*
image: woodpeckerci/plugin-docker-buildx
settings:
repo: gitea.iwanaga.moe/cjtibule/yaba/web
context: ./Web
dockerfile: ./Web/Dockerfile
tags_file: tags.txt
username:
from_secret: gitea_yaba_registry_username
password:
from_secret: gitea_yaba_registry_password
registry: gitea.iwanaga.moe
build_args:
from_secret: DEV1_BUILDARGS
- name: (YABA.Web) Package and Upload Docker Image (prod)
when:
- ref: refs/tags/WEB-*
image: woodpeckerci/plugin-docker-buildx
settings:
repo: gitea.iwanaga.moe/cjtibule/yaba/web
context: ./Web
dockerfile: ./Web/Dockerfile
tags_file: tags.txt
username:
from_secret: gitea_yaba_registry_username
password:
from_secret: gitea_yaba_registry_password
registry: gitea.iwanaga.moe
build_args:
from_secret: PROD_BUILDARGS

63
API/Readme.md Normal file
View File

@ -0,0 +1,63 @@
# YABA: Yet Another Bookmark App
## YABA.API - Developer Guide
### Running Migrations
When running migrations, .NET seems to be ignoring dependency injection settings. In order to get around this, be sure to add the connection string to the command like. For example, when adding the migration:
```
dotnet ef migrations add InitialMigration -p YABA.Data -s YABA.API --context YABABaseContext -- {CONNECTION_STRING_HERE}
```
When removing last migration:
```
dotnet ef migrations remove InitialMigration -p YABA.Data -s YABA.API --context YABABaseContext -- {CONNECTION_STRING_HERE}
```
When applying migrations:
```
dotnet ef database update -p YABA.Data -s YABA.API -c YABABaseContext -- { CONNECTION_STRING_HERE }
```
As per the documentation [on MSDN](https://learn.microsoft.com/en-ca/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-application-services):
> The -- token directs dotnet ef to treat everything that follows as an argument and not try to parse them as options. Any extra arguments not used by dotnet ef are forwarded to the app.
### Managing secrets
Best practice dictates that sensitive values should never be committed to source control. To manage secrets locally, this project utilize the [secrets manager](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-7.0&tabs=windows).
To initialize a secrets manager:
```
dotnet user-secrets init --project YABA.API
```
To set (or override) the value of a secret using a provided key:
```
dotnet user-secrets set "Key" "Value" --project YABA.API
```
```
dotnet user-secrets set "Object:Property" "Value" --project YABA.API
```
To list all secets:
```
dotnet user-secrets list --project YABA.API
```
### Docker
To build a dockerized version of the API project:
```
docker build { API_PROJECT_ROOT_SOURCE } -f { API_PROJECT_DOCKERFILE_PATH } -t { imagename:tag }
```
In order to run a container using the image built above, keep the following things in mind:
- It might be necessary to map container port 80 to another
- In the absence of a linked user secrets, secrets will have to be passed in to the container as environment variables, prefixed with `ASPNETCORE_`
```
docker run -d -p { HOST_PC_PORT_HERE }:80 --env ASPNETCORE_Object__Property=Value --name { CONTAINER_NAME } {imagename:tag}
```
Environment variables that are explicitly listed in `YABA.API\Dockerfile` will have to be properly set for proper operation of the application

38
API/YABA.API/Dockerfile Normal file
View File

@ -0,0 +1,38 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# Set environment variables
ENV ASPNETCORE_Environment=
ENV ASPNETCORE_Authentication__Auth0__ClientId=
ENV ASPNETCORE_Authentication__Auth0__ClientSecret=
ENV ASPNETCORE_Authentication__Auth0__Domain=
ENV ASPNETCORE_Authentication__Auth0__Identifier=
ENV ASPNETCORE_ConnectionStrings__YABAReadOnlyDbConnectionString=
ENV ASPNETCORE_ConnectionStrings__YABAReadWriteDbConnectionString=
ENV WebClient__Url=
ENV Serilog__WriteTo__1__Args__Uri=
ENV Serilog__WriteTo__1__Args__Labels__1__Value=
FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS build
WORKDIR /src
COPY ["YABA.API/YABA.API.csproj", "YABA.API/"]
COPY ["YABA.Common/YABA.Common.csproj", "YABA.Common/"]
COPY ["YABA.Data/YABA.Data.csproj", "YABA.Data/"]
COPY ["YABA.Models/YABA.Models.csproj", "YABA.Models/"]
COPY ["YABA.Service/YABA.Service.csproj", "YABA.Service/"]
RUN dotnet restore "YABA.API/YABA.API.csproj"
COPY . .
WORKDIR "/src/YABA.API"
RUN dotnet build "YABA.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "YABA.API.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "YABA.API.dll"]

View File

@ -2,6 +2,7 @@
using YABA.API.Extensions;
using YABA.Common.Extensions;
using YABA.Common.Lookups;
using YABA.Common.Utils;
using YABA.Service.Interfaces;
namespace YABA.API.Middlewares
@ -31,7 +32,7 @@ namespace YABA.API.Middlewares
userId = registedUser.Id;
}
httpContext.User.Identities.FirstOrDefault().AddClaim(new Claim(ClaimsLookup.UserId.GetClaimName(), userId.ToString()));
httpContext.User.Identities.FirstOrDefault().AddClaim(new Claim(ClaimsLookup.UserId.GetClaimName(EnvironmentUtils.IsDevelopmentEnvironment()), userId.ToString()));
}
}

View File

@ -16,6 +16,8 @@ using Serilog;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddEnvironmentVariables();
var configuration = builder.Configuration;
// Add services to the container.
@ -95,7 +97,7 @@ var app = builder.Build();
// Run database migrations
using (var scope = app.Services.CreateScope())
{
var yabaDbContext = scope.ServiceProvider.GetRequiredService<YABAReadWriteContext>();
var yabaDbContext = scope.ServiceProvider.GetRequiredService<YABABaseContext>();
yabaDbContext.Database.Migrate();
}
@ -118,13 +120,8 @@ app.MapControllers();
app.UseMiddleware<AddCustomClaimsMiddleware>();
app.UseMiddleware<AddCustomLoggingPropertiesMiddleware>();
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
var webClientUrl = configuration.GetSection("WebClient").GetValue<string>("Url");
app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().WithOrigins(webClientUrl));
app.UseCors(x => x.WithOrigins(webClientUrl).AllowAnyMethod().AllowAnyHeader());
app.MapHealthChecks("/Pulse");
app.Run();

View File

@ -41,6 +41,6 @@
"Enrich": ["FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId"]
},
"WebClient": {
"Url": "https://localhost:3000"
"Url": "http://localhost:3000"
}
}

View File

@ -0,0 +1,16 @@
using System;
namespace YABA.Common.Attributes
{
public class ClaimNameAttribute : Attribute
{
public string DevClaimName { get; private set; }
public string ProdClaimName { get; private set; }
public ClaimNameAttribute(string devClaimName, string prodClaimName)
{
this.DevClaimName = devClaimName;
ProdClaimName = prodClaimName;
}
}
}

View File

@ -28,9 +28,9 @@ namespace YABA.Common.Extensions
return enumValue.GetAttribute<DisplayAttribute>().Name;
}
public static string GetClaimName(this ClaimsLookup claimLookup)
public static string GetClaimName(this ClaimsLookup claimLookup, bool isDevelopmentEnvironment)
{
return claimLookup.GetAttribute<ClaimNameAttribute>().Name;
return isDevelopmentEnvironment ? claimLookup.GetAttribute<ClaimNameAttribute>().DevClaimName : claimLookup.GetAttribute<ClaimNameAttribute>().ProdClaimName;
}
public static bool IsCrudResultSuccessful(this CrudResultLookup importStatusLookup) => SuccessfulCrudStatuses.Contains(importStatusLookup);

View File

@ -1,6 +1,8 @@
using System.Security.Claims;
using System;
using System.Security.Claims;
using System.Security.Principal;
using YABA.Common.Lookups;
using YABA.Common.Utils;
namespace YABA.Common.Extensions
{
@ -12,7 +14,7 @@ namespace YABA.Common.Extensions
public static string GetCustomClaim(this IIdentity identity, ClaimsLookup claim)
{
var claimsIdentity = identity as ClaimsIdentity;
return claimsIdentity.FindFirst(claim.GetClaimName())?.Value.ToString();
return claimsIdentity.FindFirst(claim.GetClaimName(EnvironmentUtils.IsDevelopmentEnvironment()))?.Value.ToString();
}
}
}

View File

@ -0,0 +1,22 @@
using YABA.Common.Attributes;
namespace YABA.Common.Lookups
{
public enum ClaimsLookup
{
[ClaimName("https://auth.dev.iwanaga.moe/api/auth_provider_id", "https://auth.iwanaga.moe/api/auth_provider_id")]
AuthProviderId = 1,
[ClaimName("https://auth.dev.iwanaga.moe/api/email_address", "https://auth.iwanaga.moe/api/email_address")]
UserEmail = 2,
[ClaimName("https://auth.dev.iwanaga.moe/api/email_verified", "https://auth.iwanaga.moe/api/email_verified")]
IsEmailConfirmed = 3,
[ClaimName("https://auth.dev.iwanaga.moe/api/username", "https://auth.iwanaga.moe/api/username")]
Username = 4,
[ClaimName("https://auth.dev.iwanaga.moe/api/id", "https://auth.iwanaga.moe/api/id")]
UserId = 5
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace YABA.Common.Utils
{
public static class EnvironmentUtils
{
public static bool IsDevelopmentEnvironment() => Environment.GetEnvironmentVariable("ASPNETCORE_Environment") == "Development";
}
}

View File

@ -1,23 +1,7 @@
# YABA: Yet Another Bookmark App
## Developer Guides
## Developer Guide
### Running Migrations
When running migrations, .NET seems to be ignoring dependency injection settings. In order to get around this, be sure to add the connection string to the command like. For example, when adding the migration:
```
dotnet ef migrations add InitialMigration -p YABA.Data -s YABA.API --context YABABaseContext -- {CONNECTION_STRING_HERE}
```
When removing last migration:
```
dotnet ef migrations remove InitialMigration -p YABA.Data -s YABA.API --context YABABaseContext -- {CONNECTION_STRING_HERE}
```
When applying migrations:
```
dotnet ef database update -p YABA.Data -s YABA.API -c YABABaseContext -- { CONNECTION_STRING_HERE }
```
As per the documentation [on MSDN](https://learn.microsoft.com/en-ca/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-application-services):
> The -- token directs dotnet ef to treat everything that follows as an argument and not try to parse them as options. Any extra arguments not used by dotnet ef are forwarded to the app.
The application is divided into two parts. Refer to the Readme on the root of the following sections for more information:
- API: [Readme](API/Readme.md)
- Web: [Readme](Web/Readme.md)

1
Web/.dockerignore Normal file
View File

@ -0,0 +1 @@
node_modules/

22
Web/Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM node:17-alpine as builder
# Set the environment variables
ARG REACT_APP_API_BASE_URL
ARG REACT_APP_AUTH0_DOMAIN
ARG REACT_APP_AUTH0_CLIENT_ID
ARG REACT_APP_AUTH0_CALLBACK_URL
ARG REACT_APP_AUTH0_AUDIENCE
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
RUN npm run build
FROM nginx:mainline-alpine
WORKDIR /usr/share/nginx/html
RUN rm -rf ./*
COPY --from=builder /app/build .
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]

View File

@ -68,3 +68,17 @@ This section has moved here: [https://facebook.github.io/create-react-app/docs/d
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
---
## Docker
To build a dockerized version of the Web project:
```
docker build { WEB_PROJECT_ROOT_SOURCE } -f { WEB_PROJECT_DOCKERFILE_PATH } -t { imagename:tag }
```
In order to run a container using the image built above, keep the following things in mind:
- It might be necessary to map container port 80 to another
- Environment variables actively used by the web app can be overriden when starting uip a container. They are prefixed with: `REACT_APP`
- Environment variables that are explicitly listed in `Web\Dockerfile` will have to be properly set for proper operation of the application

8
Web/nginx.conf Normal file
View File

@ -0,0 +1,8 @@
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

30717
Web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More