diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml new file mode 100644 index 00000000..55545ca4 --- /dev/null +++ b/.github/workflows/ci-pr.yml @@ -0,0 +1,52 @@ +name: CI on Pull Request + +on: + pull_request: + branches: + - main + types: [opened, synchronize, reopened] + +jobs: + frontend: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + working-directory: ./frontend + + - name: Build React app + run: npm run build + working-directory: ./frontend + + - name: Run lint + run: npm run lint + working-directory: ./frontend + + backend: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Restore dependencies + run: dotnet restore + working-directory: ./backend + + - name: Build backend in Release + run: dotnet build -c Release --no-restore + working-directory: ./backend diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml new file mode 100644 index 00000000..3d01ea3d --- /dev/null +++ b/.github/workflows/ci-release.yml @@ -0,0 +1,164 @@ +name: CI/CD Release + +on: + push: + branches: + - main + +env: + AZURE_RG_NAME: rg-${{ vars.PROJECT_NAME }}-${{ vars.AZURE_RESOURCE_IDENTIFIER }} + +permissions: + id-token: write + +jobs: + + frontend: + name: Build Frontend + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: npm ci + working-directory: ./frontend + + - name: Build React app + run: npm run build + working-directory: ./frontend + + - name: Upload frontend artifact + uses: actions/upload-artifact@v4 + with: + name: frontend-build + path: ./frontend/dist + + backend: + name: Build Backend + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Restore dependencies + run: dotnet restore + working-directory: ./backend/ParkNDeploy.Api + + - name: Publish .NET app + run: dotnet publish -c Release -o ./publish + working-directory: ./backend/ParkNDeploy.Api + + - name: Upload backend artifact + uses: actions/upload-artifact@v4 + with: + name: backend-publish + path: ./backend/ParkNDeploy.Api/publish + + + deploy_infrastructure: + runs-on: ubuntu-latest + needs: [frontend, backend] + environment: production + outputs: + appServiceName: ${{ steps.bicep_deploy.outputs.appServiceName }} + staticWebAppName: ${{ steps.bicep_deploy.outputs.staticWebAppName }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + - name: Create resource group if not exists + run: | + az group show --name ${{ env.AZURE_RG_NAME }} || + az group create --name ${{ env.AZURE_RG_NAME }} --location ${{ secrets.AZURE_REGION }} + + - name: Deploy bicep + id: bicep_deploy + uses: azure/arm-deploy@v2 + with: + subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION }} + region: ${{ secrets.AZURE_REGION }} + template: ./infrastructure/main.bicep + parameters: project=${{ vars.PROJECT_NAME }} location=${{ secrets.AZURE_REGION }} swaLocation=${{ secrets.AZURE_SWA_REGION }} identifier=${{ vars.AZURE_RESOURCE_IDENTIFIER }} + resourceGroupName: ${{ env.AZURE_RG_NAME }} + + deploy_backend: + runs-on: ubuntu-latest + needs: deploy_infrastructure + environment: production + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download backend artifact + uses: actions/download-artifact@v4 + with: + name: backend-publish + path: ./backend_publish + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Deploy backend to App Service + uses: azure/webapps-deploy@v2 + with: + app-name: ${{ needs.deploy_infrastructure.outputs.appServiceName }} + package: ./backend_publish + + + deploy_frontend: + runs-on: ubuntu-latest + needs: deploy_infrastructure + environment: production + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download frontend artifact + uses: actions/download-artifact@v4 + with: + name: frontend-build + path: ./frontend_dist + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Get Static Web App deployment token + run: | + SWA_DEPLOYMENT_TOKEN=$(az staticwebapp secrets list -n ${{ needs.deploy_infrastructure.outputs.staticWebAppName }} -o tsv --query properties.apiKey) + echo SWA_DEPLOYMENT_TOKEN=$SWA_DEPLOYMENT_TOKEN >> $GITHUB_ENV + + - name: Deploy frontend to Static Web App + uses: azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ env.SWA_DEPLOYMENT_TOKEN }} + app_location: frontend_dist + action: upload + skip_app_build: true + skip_api_build: true \ No newline at end of file diff --git a/.github/workflows/deploy-infra-and-apps.yml b/.github/workflows/deploy-infra-and-apps.yml new file mode 100644 index 00000000..dfb8cac6 --- /dev/null +++ b/.github/workflows/deploy-infra-and-apps.yml @@ -0,0 +1,116 @@ + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: + + +env: + AZURE_RG_NAME: rg-${{ vars.PROJECT_NAME }}-${{ vars.AZURE_RESOURCE_IDENTIFIER }} + +permissions: + id-token: write + +jobs: + deploy_infrastructure: + runs-on: ubuntu-latest + environment: production + outputs: + appServiceName: ${{ steps.bicep_deploy.outputs.appServiceName }} + staticWebAppName: ${{ steps.bicep_deploy.outputs.staticWebAppName }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + + - name: Create resource group if not exists + run: | + az group show --name ${{ env.AZURE_RG_NAME }} || + az group create --name ${{ env.AZURE_RG_NAME }} --location ${{ secrets.AZURE_REGION }} + + - name: Deploy bicep + id: bicep_deploy + uses: azure/arm-deploy@v2 + with: + subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION }} + region: ${{ secrets.AZURE_REGION }} + template: ./infrastructure/main.bicep + parameters: project=${{ vars.PROJECT_NAME }} location=${{ secrets.AZURE_REGION }} swaLocation=${{ secrets.AZURE_SWA_REGION }} identifier=${{ vars.AZURE_RESOURCE_IDENTIFIER }} + resourceGroupName: ${{ env.AZURE_RG_NAME }} + + deploy_backend: + runs-on: ubuntu-latest + needs: deploy_infrastructure + environment: production + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK 9.0.x + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Publish the app + run: dotnet publish -c Release --propertyublishDir=publish # Publish the app to the API project publish folder + working-directory: ./backend # Specify where to find the solution file in repository + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Deploy backend to App Service + uses: azure/webapps-deploy@v2 + with: + app-name: ${{ needs.deploy_infrastructure.outputs.appServiceName }} # Access to the previous job output to get the appServiceName deployed with bicep + package: ./backend/ParkNDeploy.Api/publish # Path to the previously published app + # deploy_infrastructure ... + + deploy_frontend: + runs-on: ubuntu-latest + needs: deploy_infrastructure + environment: production + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build the app + run: npm install && npm run build + working-directory: ./frontend + + - name: Login to Azure + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Get Static Web App deployment token + run: | + SWA_DEPLOYMENT_TOKEN=$(az staticwebapp secrets list -n ${{ needs.deploy_infrastructure.outputs.staticWebAppName }} -o tsv --query properties.apiKey) + echo SWA_DEPLOYMENT_TOKEN=$SWA_DEPLOYMENT_TOKEN >> $GITHUB_ENV + + - name: Deploy frontend to Static Web App + uses: azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ env.SWA_DEPLOYMENT_TOKEN }} + app_location: frontend/dist + action: upload + skip_app_build: true + skip_api_build: true + + diff --git a/backend/ParkNDeploy.Api/appsettings.Development.json b/backend/ParkNDeploy.Api/appsettings.Development.json index 0c208ae9..f0a9a14a 100644 --- a/backend/ParkNDeploy.Api/appsettings.Development.json +++ b/backend/ParkNDeploy.Api/appsettings.Development.json @@ -3,6 +3,7 @@ "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" + } } } diff --git a/frontend/index.html b/frontend/index.html index 0a5274f1..f159ba7d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,7 +5,7 @@ ParkNDeploy - + \\coucou modifSDQSDCS
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f4a4f550..569a872e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "0.0.0", + "version": "1.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "0.0.0", + "version": "1.2.3", "dependencies": { "@radix-ui/react-icons": "^1.3.2", "@tanstack/react-query": "^5.84.1", diff --git a/frontend/package.json b/frontend/package.json index a0946957..7d8557ea 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.0.0", + "version": "1.2.3", "type": "module", "scripts": { "dev": "vite --port 5175", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 986ff9bb..0cb47eb4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,6 +23,8 @@ function App() {

Where can I Park in Angers ? 👀

+

ParkNDeploy

+

Version: {APP_VERSION}

{ console.log(parkingName); diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 11f02fe2..354c5b3b 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1 +1,3 @@ /// + +declare const APP_VERSION: string; \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ed788acb..754cd9fc 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,23 +1,22 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import path from 'path' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import path from "path"; +import pkg from "./package.json"; -// https://vitejs.dev/config/ export default defineConfig({ - server:{ - proxy: - { - // redirect request on /api to target specified - '/api': { - target: 'https://localhost:7085/', // whether you're running your local api with kestrel or behind IIS Express, the port mind change - secure: false, // Do not verify SSL certificates + server: { + proxy: { + "/api": { + target: "https://localhost:7085/", + secure: false, }, - } - }, - resolve: { - alias: { - '@': path.resolve(__dirname, 'src'), }, - }, + }, + resolve: { + alias: { "@": path.resolve(__dirname, "src") }, + }, plugins: [react()], -}) + define: { + APP_VERSION: JSON.stringify(process.env.VITE_APP_VERSION ?? pkg.version), + }, +}); diff --git a/infrastructure/main.bicep b/infrastructure/main.bicep new file mode 100644 index 00000000..67cb2dcf --- /dev/null +++ b/infrastructure/main.bicep @@ -0,0 +1,53 @@ +targetScope = 'resourceGroup' // We'll deploy the resources in the provided resource group + +// Parameters to easily construct resource names +param location string +param project string + +// Here we'll add an identifier to create a unique name for the App Service Plan, for example your trigram, so that everyone could deploy his own parkndeploy instance +param identifier string + +param swaLocation string +// Create the AppServicePlan through the AppServicePlan module +module appServicePlan 'modules/appServicePlan.bicep' = { + name: 'appServicePlan' + params: { + location: location + project: project + identifier: identifier + } +} + +// Create the AppService through the AppService module +module appService 'modules/appService.bicep' = { + name: 'appService' + params: { + location: location + project: project + identifier: identifier + planId: appServicePlan.outputs.planId // Use the appServicePlan output to get its id back => an App Service needs to reference its App Service Plan + } +} +module staticWebApp 'modules/staticWebApp.bicep' = { + name: 'staticWebApp' + params: { + location: swaLocation + project: project + identifier: identifier + } +} +// Previous resources +// ... +module staticWebAppBackend 'modules/staticWebAppBackend.bicep' = { + name: 'staticWebAppBackend' + params: { + backendBindedResourceId: appService.outputs.appServiceId + swaName: staticWebApp.outputs.swaName + location: location + } +} + +// Outputs +// ... +output appServiceName string = appService.outputs.appServiceName // Export AppServiceName in order to deploy the API later on +output staticWebAppName string = staticWebApp.outputs.swaName // Export StaticWebAppName in order to deploy the Frontend late diff --git a/infrastructure/modules/appService.bicep b/infrastructure/modules/appService.bicep new file mode 100644 index 00000000..c797e14f --- /dev/null +++ b/infrastructure/modules/appService.bicep @@ -0,0 +1,25 @@ +param location string +param project string +param identifier string + +// App Service Plan identifier that will host our App Service +param planId string + +resource app 'Microsoft.Web/sites@2022-03-01' = { + name: '${project}-app-${identifier}' + location: location + + properties: { + serverFarmId: planId + reserved: true + + siteConfig: { + linuxFxVersion: 'DOTNETCORE|9.0' // Specify to setup the .NET Core 9.0 runtime (used by our backend API) on the Linux machine under the hood + } + } +} + +output appServiceName string = app.name // Export the App Service name for deployment +// Previous outputs +// ... +output appServiceId string = app.id diff --git a/infrastructure/modules/appServicePlan.bicep b/infrastructure/modules/appServicePlan.bicep new file mode 100644 index 00000000..c871995f --- /dev/null +++ b/infrastructure/modules/appServicePlan.bicep @@ -0,0 +1,20 @@ +param location string +param project string +param identifier string + +resource plan 'Microsoft.Web/serverfarms@2022-09-01' = { + name: '${project}-plan-${identifier}' + location: location + + sku: { + name: 'F1' // We use F1 pricing plan (free one) as we don't need specific features + } + + kind: 'app,linux' // Allow to deploy on an App Service using Linux OS + + properties: { + reserved: true // Specifity of App Service with Linux OS + } +} + +output planId string = plan.id // Export the App Service identifier \ No newline at end of file diff --git a/infrastructure/modules/staticWebApp.bicep b/infrastructure/modules/staticWebApp.bicep new file mode 100644 index 00000000..290ed72c --- /dev/null +++ b/infrastructure/modules/staticWebApp.bicep @@ -0,0 +1,16 @@ +param location string +param project string +param identifier string + +resource swa 'Microsoft.Web/staticSites@2024-04-01' = { + name: '${project}-swa-${identifier}' + location: location + + sku: { + name: 'Standard' + tier: 'Standard' +} + properties: {} // Even empty, it's mandatory ... +} + +output swaName string = swa.name // Expose Static Web App name as we did for App Service for deployment purpose diff --git a/infrastructure/modules/staticWebAppBackend.bicep b/infrastructure/modules/staticWebAppBackend.bicep new file mode 100644 index 00000000..4e902770 --- /dev/null +++ b/infrastructure/modules/staticWebAppBackend.bicep @@ -0,0 +1,11 @@ +param backendBindedResourceId string +param swaName string +param location string + +resource staticWebAppBackend 'Microsoft.Web/staticSites/linkedBackends@2022-03-01' = { + name: '${swaName}/backend' + properties: { + backendResourceId: backendBindedResourceId + region: location + } +}