diff --git a/.github/workflows/build-and-push-image.yml b/.github/workflows/build-and-push-image.yml
index 1534c962b..ce95f1f6e 100644
--- a/.github/workflows/build-and-push-image.yml
+++ b/.github/workflows/build-and-push-image.yml
@@ -49,18 +49,35 @@ jobs:
name: Deploy '${{ needs.set-env.outputs.branch }}' to ${{ needs.set-env.outputs.environment }}
needs: [ set-env ]
uses: DFE-Digital/deploy-azure-container-apps-action/.github/workflows/build-push-deploy.yml@v2.2.0
+ strategy:
+ matrix:
+ image: [
+ "Dockerfile",
+ "Dockerfile.PersonsApi"
+ ]
+ include:
+ - image: "Dockerfile"
+ aca_name_secret: "AZURE_ACA_NAME"
+ prefix: ""
+ name: "tramsapi-app"
+ - image: "Dockerfile.PersonsApi"
+ aca_name_secret: "AZURE_PERSONS_API_ACA_NAME"
+ prefix: "persons-api-"
+ name: "personsapi-app"
with:
- docker-image-name: 'tramsapi-app'
- docker-build-file-name: './Dockerfile'
+ docker-image-name: '${{ matrix.name }}'
+ docker-build-file-name: './${{ matrix.image }}'
+ docker-tag-prefix: ${{ matrix.prefix }}
environment: ${{ needs.set-env.outputs.environment }}
- annotate-release: true
+ # Only annotate the release once, because both apps are deployed at the same time
+ annotate-release: ${{ matrix.name == 'tramsapi-app' }}
docker-build-args: |
COMMIT_SHA="${{ needs.set-env.outputs.checked-out-sha }}"
secrets:
azure-acr-name: ${{ secrets.ACR_NAME }}
azure-acr-credentials: ${{ secrets.ACR_CREDENTIALS }}
azure-aca-credentials: ${{ secrets.AZURE_ACA_CREDENTIALS }}
- azure-aca-name: ${{ secrets.AZURE_ACA_NAME }}
+ azure-aca-name: ${{ secrets[matrix.aca_name_secret] }}
azure-aca-resource-group: ${{ secrets.AZURE_ACA_RESOURCE_GROUP }}
create-tag:
diff --git a/.github/workflows/continuous-integration-dotnet.yml b/.github/workflows/continuous-integration-dotnet.yml
index 4429809bc..fe5af2b1b 100644
--- a/.github/workflows/continuous-integration-dotnet.yml
+++ b/.github/workflows/continuous-integration-dotnet.yml
@@ -7,6 +7,7 @@ on:
paths:
- 'Dfe.Academies.*/**'
- 'TramsDataApi*/**'
+ - 'PersonsApi*/**'
- '!Dfe.Academies.Performance/**'
pull_request:
branches: [ main ]
@@ -14,6 +15,7 @@ on:
paths:
- 'Dfe.Academies.*/**'
- 'TramsDataApi*/**'
+ - 'PersonsApi*/**'
- '!Dfe.Academies.Performance/**'
env:
@@ -73,7 +75,7 @@ jobs:
- name: Install dotnet reportgenerator
run: dotnet tool install --global dotnet-reportgenerator-globaltool
-
+
- name: Add nuget package source
run: dotnet nuget add source --username USERNAME --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/DFE-Digital/index.json"
@@ -81,7 +83,7 @@ jobs:
run: dotnet tool restore
- name: Restore dependencies
- run: dotnet restore TramsDataApi.sln
+ run: dotnet restore
- name: Build, Test and Analyze
env:
@@ -97,4 +99,4 @@ jobs:
- name: Stop containers
if: always()
- run: docker-compose -f "docker-compose.yml" down
+ run: docker compose -f "docker-compose.yml" down
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 35c09a140..16deefd1d 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -4,11 +4,18 @@ on:
pull_request:
paths:
- Dockerfile
+ - Dockerfile.PersonsApi
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ image: [
+ "Dockerfile",
+ "Dockerfile.PersonsApi"
+ ]
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -19,5 +26,6 @@ jobs:
- name: Build docker image
uses: docker/build-push-action@v6
with:
+ file: './${{ matrix.image }}'
secrets: github_token=${{ secrets.GITHUB_TOKEN }}
push: false
diff --git a/CypressTests/cypress.env.json b/CypressTests/cypress.env.json
new file mode 100644
index 000000000..b09f54849
--- /dev/null
+++ b/CypressTests/cypress.env.json
@@ -0,0 +1,7 @@
+{
+ "url": "https://localhost",
+ "personsUrl": "https://localhost:7089",
+ "api": "https://localhost:7089",
+ "apiKey": "app-key",
+ "authKey": "app-key"
+}
\ No newline at end of file
diff --git a/Dfe.Academies.Api.Infrastructure/Dfe.Academies.Infrastructure.csproj b/Dfe.Academies.Api.Infrastructure/Dfe.Academies.Infrastructure.csproj
index 68cd9b939..aed6a2c15 100644
--- a/Dfe.Academies.Api.Infrastructure/Dfe.Academies.Infrastructure.csproj
+++ b/Dfe.Academies.Api.Infrastructure/Dfe.Academies.Infrastructure.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/Dfe.Academies.Api.Infrastructure/MopContext.cs b/Dfe.Academies.Api.Infrastructure/MopContext.cs
new file mode 100644
index 000000000..78a4a4c80
--- /dev/null
+++ b/Dfe.Academies.Api.Infrastructure/MopContext.cs
@@ -0,0 +1,64 @@
+using Dfe.Academies.Domain.Persons;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Dfe.Academies.Academisation.Data;
+
+public class MopContext : DbContext
+{
+ const string DEFAULT_SCHEMA = "mop";
+
+ public MopContext()
+ {
+
+ }
+
+ public MopContext(DbContextOptions options) : base(options)
+ {
+
+ }
+
+ public DbSet MemberContactDetails { get; set; } = null!;
+ public DbSet Constituencies { get; set; } = null!;
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (!optionsBuilder.IsConfigured)
+ {
+ optionsBuilder.UseSqlServer("Server=localhost;Database=sip;Integrated Security=true;TrustServerCertificate=True");
+ }
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(ConfigureMemberContactDetails);
+ modelBuilder.Entity(ConfigureConstituency);
+
+ base.OnModelCreating(modelBuilder);
+ }
+
+
+ private void ConfigureMemberContactDetails(EntityTypeBuilder memberContactDetailsConfiguration)
+ {
+ memberContactDetailsConfiguration.HasKey(e => e.MemberID);
+
+ memberContactDetailsConfiguration.ToTable("MemberContactDetails", DEFAULT_SCHEMA);
+ memberContactDetailsConfiguration.Property(e => e.MemberID).HasColumnName("memberID");
+ memberContactDetailsConfiguration.Property(e => e.Email).HasColumnName("email");
+ memberContactDetailsConfiguration.Property(e => e.TypeId).HasColumnName("typeId");
+ }
+
+ private void ConfigureConstituency(EntityTypeBuilder constituencyConfiguration)
+ {
+ constituencyConfiguration.ToTable("Constituencies", DEFAULT_SCHEMA);
+ constituencyConfiguration.Property(e => e.ConstituencyId).HasColumnName("constituencyId");
+ constituencyConfiguration.Property(e => e.ConstituencyName).HasColumnName("constituencyName");
+ constituencyConfiguration.Property(e => e.NameList).HasColumnName("nameListAs");
+ constituencyConfiguration.Property(e => e.NameDisplayAs).HasColumnName("nameDisplayAs");
+ constituencyConfiguration.Property(e => e.NameFullTitle).HasColumnName("nameFullTitle");
+ constituencyConfiguration.Property(e => e.NameFullTitle).HasColumnName("nameFullTitle");
+ constituencyConfiguration.Property(e => e.LastRefresh).HasColumnName("lastRefresh");
+ }
+
+
+}
diff --git a/Dfe.Academies.Api.Infrastructure/MopRepository.cs b/Dfe.Academies.Api.Infrastructure/MopRepository.cs
new file mode 100644
index 000000000..3442a8faa
--- /dev/null
+++ b/Dfe.Academies.Api.Infrastructure/MopRepository.cs
@@ -0,0 +1,12 @@
+using Dfe.Academies.Academisation.Data;
+using Dfe.Academies.Infrastructure.Repositories;
+
+namespace Dfe.Academies.Infrastructure
+{
+ public class MopRepository : Repository where TEntity : class, new()
+ {
+ public MopRepository(MopContext dbContext) : base(dbContext)
+ {
+ }
+ }
+}
diff --git a/Dfe.Academies.Api.Infrastructure/Repositories/Repository.cs b/Dfe.Academies.Api.Infrastructure/Repositories/Repository.cs
new file mode 100644
index 000000000..58ff568d4
--- /dev/null
+++ b/Dfe.Academies.Api.Infrastructure/Repositories/Repository.cs
@@ -0,0 +1,186 @@
+using Dfe.Academies.Domain.Repositories;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using System.Linq.Expressions;
+
+namespace Dfe.Academies.Infrastructure.Repositories
+{
+ public abstract class Repository : IRepository
+ where TEntity : class, new()
+ where TDbContext : DbContext
+ {
+ ///
+ /// The
+ ///
+ protected readonly TDbContext DbContext;
+
+ /// Constructor
+ ///
+ protected Repository(TDbContext dbContext) => this.DbContext = dbContext;
+
+ /// Short hand for _dbContext.Set
+ ///
+ protected virtual DbSet DbSet()
+ {
+ return this.DbContext.Set();
+ }
+
+ ///
+ public virtual IQueryable Query() => (IQueryable)this.DbSet();
+
+ ///
+ public virtual ICollection Fetch(Expression> predicate)
+ {
+ return (ICollection)((IQueryable)this.DbSet()).Where(predicate).ToList();
+ }
+
+ ///
+ public virtual async Task> FetchAsync(
+ Expression> predicate,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return (ICollection)await EntityFrameworkQueryableExtensions.ToListAsync(((IQueryable)this.DbSet()).Where(predicate), cancellationToken);
+ }
+
+ ///
+ public virtual TEntity Find(params object[] keyValues) => this.DbSet().Find(keyValues);
+
+ ///
+ public virtual async Task FindAsync(params object[] keyValues)
+ {
+ return await this.DbSet().FindAsync(keyValues);
+ }
+
+ ///
+ public virtual TEntity Find(Expression> predicate)
+ {
+ return ((IQueryable)this.DbSet()).FirstOrDefault(predicate);
+ }
+
+ ///
+ public virtual async Task FindAsync(
+ Expression> predicate,
+ CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return await EntityFrameworkQueryableExtensions.FirstOrDefaultAsync((IQueryable)this.DbSet(), predicate, cancellationToken);
+ }
+
+ ///
+ public virtual TEntity Get(params object[] keyValues)
+ {
+ return this.Find(keyValues) ?? throw new InvalidOperationException(string.Format("Entity type {0} is null for primary key {1}", (object)typeof(TEntity), (object)keyValues));
+ }
+
+ ///
+ public virtual async Task GetAsync(params object[] keyValues)
+ {
+ return await this.FindAsync(keyValues) ?? throw new InvalidOperationException(string.Format("Entity type {0} is null for primary key {1}", (object)typeof(TEntity), (object)keyValues));
+ }
+
+ ///
+ public virtual TEntity Get(Expression> predicate)
+ {
+ return ((IQueryable)this.DbSet()).Single(predicate);
+ }
+
+ ///
+ public virtual async Task GetAsync(Expression> predicate)
+ {
+ return await EntityFrameworkQueryableExtensions.SingleAsync((IQueryable)this.DbSet(), predicate, new CancellationToken());
+ }
+
+ ///
+ public virtual TEntity Add(TEntity entity)
+ {
+ this.DbContext.Add(entity);
+ this.DbContext.SaveChanges();
+ return entity;
+ }
+
+ ///
+ public virtual async Task AddAsync(TEntity entity, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ EntityEntry entityEntry = await this.DbContext.AddAsync(entity, cancellationToken);
+ int num = await this.DbContext.SaveChangesAsync(cancellationToken);
+ return entity;
+ }
+
+ ///
+ public virtual IEnumerable AddRange(ICollection entities)
+ {
+ this.DbContext.AddRange((IEnumerable