Skip to content

Commit 53acf3d

Browse files
authored
Merge pull request #37 from IntelliTect-Samples/bot-management-api
Bot Net Management API is set up lgtu
2 parents e180823 + 7af2e3d commit 53acf3d

27 files changed

Lines changed: 1564 additions & 0 deletions
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Build and Deploy to Azure Container Apps
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "BotNetApi/**"
8+
9+
# Required for OIDC federated identity authentication — no client secrets used
10+
permissions:
11+
id-token: write
12+
contents: read
13+
14+
env:
15+
RESOURCE_GROUP: ewu-deliverybotsystem-rg
16+
17+
# ACR — pre-created, admin credentials stored as Container App registry secret
18+
ACR_NAME: DeliverybotCR
19+
ACR_LOGIN_SERVER: deliverybotcr.azurecr.io
20+
21+
# Container App — pre-created with system-assigned managed identity
22+
CONTAINER_APP_NAME: ewu-deliverybotsystem-api
23+
24+
# Azure SQL — pre-created; Container App MI already has db_owner in BotNetApiDb
25+
SQL_SERVER_NAME: deliverybotsystem-sql
26+
SQL_DB_NAME: BotNetApiDb
27+
28+
IMAGE_NAME: botnetapi
29+
30+
jobs:
31+
build-and-deploy:
32+
runs-on: ubuntu-latest
33+
steps:
34+
# ── 1. Checkout source code ───────────────────────────────────────────────
35+
- name: Checkout repository
36+
uses: actions/checkout@v4
37+
38+
# ── 2. Authenticate to Azure via OIDC federated identity ─────────────────
39+
- name: Azure Login (OIDC)
40+
uses: azure/login@v2
41+
with:
42+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
43+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
44+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
45+
46+
# ── 3. Build Docker image and push to ACR ────────────────────────────────
47+
- name: Log in to Azure Container Registry
48+
run: az acr login --name "$ACR_NAME"
49+
50+
- name: Build and push Docker image
51+
run: |
52+
IMAGE_TAG="${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${{ github.sha }}"
53+
echo "Building: $IMAGE_TAG"
54+
docker build -t "$IMAGE_TAG" -f Dockerfile .
55+
docker push "$IMAGE_TAG"
56+
echo "IMAGE_TAG=$IMAGE_TAG" >> "$GITHUB_ENV"
57+
58+
# ── 4. Deploy to Container App ────────────────────────────────────────────
59+
# EF Core migrations run automatically at startup via db.Database.Migrate().
60+
# The Container App's managed identity already has db_owner on BotNetApiDb.
61+
- name: Update Container App image and environment
62+
run: |
63+
SQL_CONN="Server=tcp:${SQL_SERVER_NAME}.database.windows.net,1433;Initial Catalog=${SQL_DB_NAME};Authentication=Active Directory Managed Identity;"
64+
65+
az containerapp update \
66+
--name "$CONTAINER_APP_NAME" \
67+
--resource-group "$RESOURCE_GROUP" \
68+
--image "$IMAGE_TAG" \
69+
--set-env-vars \
70+
"ASPNETCORE_ENVIRONMENT=Production" \
71+
"ConnectionStrings__DefaultConnection=${SQL_CONN}"
72+
73+
# ── 5. Output the live deployment URL ─────────────────────────────────────
74+
- name: Print deployment URL
75+
run: |
76+
FQDN=$(az containerapp show \
77+
--name "$CONTAINER_APP_NAME" \
78+
--resource-group "$RESOURCE_GROUP" \
79+
--query properties.configuration.ingress.fqdn -o tsv)
80+
echo "========================================"
81+
echo " Deployment complete!"
82+
echo " App URL : https://${FQDN}"
83+
echo " Bots API: https://${FQDN}/api/bots"
84+
echo "========================================"

‎.gitignore‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# ──────────────────────────────────────────────
2+
# Docker
3+
# ──────────────────────────────────────────────
4+
5+
# Local override compose files
6+
docker-compose.override.yml
7+
8+
# ──────────────────────────────────────────────
9+
# Node / Frontend
10+
# ──────────────────────────────────────────────
11+
12+
**/node_modules/
13+
**/dist/
14+
**/build/
15+
.next/
16+
.nuxt/
17+
.output/
18+
19+
# ──────────────────────────────────────────────
20+
# OS / Editor noise
21+
# ──────────────────────────────────────────────
22+
23+
.DS_Store
24+
Thumbs.db
25+
*.swp
26+
*.swo
27+
*~
28+
29+
# VS Code workspace settings (keep launch/tasks, ignore local overrides)
30+
.vscode/settings.json
31+
.vscode/*.code-workspace

‎BotNetApi/.gitignore‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# ──────────────────────────────────────────────
2+
# .NET / ASP.NET Core — BotNetApi
3+
# ──────────────────────────────────────────────
4+
5+
# Build output
6+
bin/
7+
obj/
8+
9+
# Published output
10+
publish/
11+
out/
12+
13+
# NuGet packages (restored automatically, never committed)
14+
*.nupkg
15+
*.snupkg
16+
packages/
17+
!packages/build/
18+
19+
# User-specific Visual Studio files
20+
*.user
21+
*.suo
22+
*.userosscache
23+
*.sln.docstates
24+
.vs/
25+
26+
# Rider IDE
27+
.idea/
28+
29+
# ──────────────────────────────────────────────
30+
# Local environment / secrets
31+
# ──────────────────────────────────────────────
32+
33+
# Keep production appsettings, ignore local overrides with real connection strings
34+
appsettings.Development.json
35+
secrets.json
36+
UserSecrets/

‎BotNetApi/BotNetApi.csproj‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8">
11+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
12+
<PrivateAssets>all</PrivateAssets>
13+
</PackageReference>
14+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.8" />
15+
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.8">
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
<PrivateAssets>all</PrivateAssets>
18+
</PackageReference>
19+
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
20+
</ItemGroup>
21+
22+
</Project>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using BotNetApi.DTOs;
2+
using BotNetApi.Services;
3+
using Microsoft.AspNetCore.Mvc;
4+
5+
namespace BotNetApi.Controllers;
6+
7+
[ApiController]
8+
[Route("api/bots")]
9+
public class BotsController : ControllerBase
10+
{
11+
private readonly IBotService _botService;
12+
13+
public BotsController(IBotService botService)
14+
{
15+
_botService = botService;
16+
}
17+
18+
// GET /api/bots
19+
[HttpGet]
20+
public async Task<IActionResult> GetAll()
21+
{
22+
var bots = await _botService.GetAllAsync();
23+
return Ok(bots);
24+
}
25+
26+
// GET /api/bots/{id}
27+
[HttpGet("{id:int}")]
28+
public async Task<IActionResult> GetById(int id)
29+
{
30+
var bot = await _botService.GetByIdAsync(id);
31+
return bot is null ? NotFound() : Ok(bot);
32+
}
33+
34+
// POST /api/bots
35+
[HttpPost]
36+
public async Task<IActionResult> Create([FromBody] CreateBotDto dto)
37+
{
38+
var created = await _botService.CreateAsync(dto);
39+
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
40+
}
41+
42+
// PUT /api/bots/{id}
43+
[HttpPut("{id:int}")]
44+
public async Task<IActionResult> Update(int id, [FromBody] UpdateBotDto dto)
45+
{
46+
var updated = await _botService.UpdateAsync(id, dto);
47+
return updated is null ? NotFound() : Ok(updated);
48+
}
49+
50+
// DELETE /api/bots/{id}
51+
[HttpDelete("{id:int}")]
52+
public async Task<IActionResult> Delete(int id)
53+
{
54+
var deleted = await _botService.DeleteAsync(id);
55+
return deleted ? NoContent() : NotFound();
56+
}
57+
58+
// PUT /api/bots/{id}/recharge
59+
[HttpPut("{id:int}/recharge")]
60+
public async Task<IActionResult> Recharge(int id)
61+
{
62+
var updated = await _botService.RechargeAsync(id);
63+
return updated is null ? NotFound() : Ok(updated);
64+
}
65+
66+
// PUT /api/bots/{id}/servicing-status
67+
[HttpPut("{id:int}/servicing-status")]
68+
public async Task<IActionResult> UpdateServicingStatus(int id, [FromBody] UpdateServicingStatusDto dto)
69+
{
70+
var updated = await _botService.UpdateServicingStatusAsync(id, dto);
71+
return updated is null ? NotFound() : Ok(updated);
72+
}
73+
}

‎BotNetApi/DTOs/BotResponseDto.cs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace BotNetApi.DTOs;
2+
3+
public class BotResponseDto
4+
{
5+
public int Id { get; set; }
6+
public string Name { get; set; } = string.Empty;
7+
public int BatteryLevel { get; set; }
8+
public DateTime LastUpdated { get; set; }
9+
public bool IsOnline { get; set; }
10+
public bool IsServicingCustomer { get; set; }
11+
}

‎BotNetApi/DTOs/CreateBotDto.cs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace BotNetApi.DTOs;
4+
5+
public class CreateBotDto
6+
{
7+
[Required]
8+
[MaxLength(100)]
9+
public string Name { get; set; } = string.Empty;
10+
11+
[Range(0, 100, ErrorMessage = "BatteryLevel must be between 0 and 100.")]
12+
public int BatteryLevel { get; set; } = 100;
13+
14+
public bool IsOnline { get; set; } = true;
15+
}

‎BotNetApi/DTOs/UpdateBotDto.cs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace BotNetApi.DTOs;
4+
5+
public class UpdateBotDto
6+
{
7+
[Required]
8+
[MaxLength(100)]
9+
public string Name { get; set; } = string.Empty;
10+
11+
[Range(0, 100, ErrorMessage = "BatteryLevel must be between 0 and 100.")]
12+
public int BatteryLevel { get; set; }
13+
14+
public bool IsOnline { get; set; }
15+
16+
public bool IsServicingCustomer { get; set; }
17+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace BotNetApi.DTOs;
2+
3+
public class UpdateServicingStatusDto
4+
{
5+
public bool IsServicingCustomer { get; set; }
6+
}

‎BotNetApi/Data/AppDbContext.cs‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using BotNetApi.Models;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
namespace BotNetApi.Data;
5+
6+
public class AppDbContext : DbContext
7+
{
8+
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
9+
10+
public DbSet<Bot> Bots => Set<Bot>();
11+
12+
protected override void OnModelCreating(ModelBuilder modelBuilder)
13+
{
14+
modelBuilder.Entity<Bot>(entity =>
15+
{
16+
entity.HasKey(b => b.Id);
17+
entity.Property(b => b.Name).IsRequired().HasMaxLength(100);
18+
});
19+
}
20+
}

0 commit comments

Comments
 (0)