Compare commits

...

8 Commits

Author SHA1 Message Date
47e2117325 Add frontend to backend dependency 2023-10-30 23:14:01 +01:00
6e5f0fb312 Docker nextjs 2023-10-30 23:13:17 +01:00
7d54a53005 Add netxjs app 2023-10-30 22:14:49 +01:00
c337c3dfd5 Resolve all warnings 2023-10-30 22:08:02 +01:00
08983ba30d Adjust 2023-10-30 21:58:56 +01:00
5792d2fcf4 Add database commits 2023-10-30 21:22:06 +01:00
6556e17146 Add first sanity check 2023-10-30 21:20:19 +01:00
d3d2b4b989 Implement generic repo pattern and unitofwork 2023-10-30 20:56:37 +01:00
34 changed files with 5031 additions and 211 deletions

View File

@@ -8,12 +8,13 @@ public class ApplicationContext : DbContext
: base(options)
{
}
public DbSet<Gerbil> Gerbils { get; set; }
public DbSet<Litter> Litters { get; set; }
public DbSet<Breeder> Breeders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Gerbil>().ToTable("Gerbils");
modelBuilder.Entity<Litter>().ToTable("Litters");
modelBuilder.Entity<Breeder>().ToTable("Breeders");
modelBuilder.Entity<Gerbil>().HasOne(entity => entity.Litter);
modelBuilder.Entity<Litter>().HasOne(entity => entity.Father);
modelBuilder.Entity<Litter>().HasOne(entity => entity.Mother);

View File

@@ -1,5 +1,5 @@
using GerbilManagerWebAPI.Models;
using GerbilManagerWebAPI.Repositories;
using GerbilManagerWebAPI.DAL;
using GerbilManagerWebAPI.Dtos;
using GerbilManagerWebAPI.Converter;
@@ -13,20 +13,18 @@ namespace GerbilManagerWebAPI.Controllers
[Route("breeders")]
public class BreederController : ControllerBase
{
private readonly IGerbilRepository gerbilrepository;
private readonly IBreederRepository repository;
private readonly ILitterRepository litterRepository;
private readonly UnitOfWork unitOfWork;
public BreederController(IBreederRepository repo)
public BreederController(UnitOfWork unitOfWork)
{
this.repository = repo;
this.unitOfWork = unitOfWork;
}
//GET /gerbils
[HttpGet]
public IEnumerable<BreederDto> GetBreeders()
{
var items = repository.GetBreeders().Select( breeder => breeder.AsDto());
var items = unitOfWork.BreederRepository.Get().Select( breeder => breeder.AsDto());
return items;
}
@@ -34,7 +32,7 @@ namespace GerbilManagerWebAPI.Controllers
[HttpGet("{id}")]
public ActionResult<BreederDto> GetBreeder(Guid id)
{
var item = repository.GetBreeder(id).AsDto();
var item = unitOfWork.BreederRepository.GetByID(id).AsDto();
if(item is null)
{
@@ -51,7 +49,8 @@ namespace GerbilManagerWebAPI.Controllers
Name = dto.Name,
};
this.repository.CreateBreeder(breeder);
this.unitOfWork.BreederRepository.Insert(breeder);
this.unitOfWork.Save();
return CreatedAtAction(nameof(GetBreeder), new { id = breeder.Id}, breeder.AsDto());
}
@@ -59,31 +58,33 @@ namespace GerbilManagerWebAPI.Controllers
[HttpPut("{id}")]
public ActionResult UpdateBreeder(Guid id, UpdateBreederDto breederDto)
{
var existingItem = repository.GetBreeder(id);
var existingItem = unitOfWork.BreederRepository.GetByID(id);
if(existingItem is null)
{
return NotFound();
}
Breeder updatedBreeder = existingItem with{
Name = breederDto.Name
Breeder updatedBreeder = existingItem with {
Name = breederDto?.Name!
};
repository.UpdateBreeder(updatedBreeder);
unitOfWork.BreederRepository.Update(updatedBreeder);
this.unitOfWork.Save();
return NoContent();
}
[HttpDelete("{id}")]
public ActionResult DeleteBreeder(Guid id)
{
var existingLitter = repository.GetBreeder(id);
var existingLitter = unitOfWork.BreederRepository.GetByID(id);
if(existingLitter is null)
{
return NotFound();
}
repository.DeleteBreeder(existingLitter);
unitOfWork.BreederRepository.Delete(existingLitter);
this.unitOfWork.Save();
return NoContent();
}

View File

@@ -1,5 +1,5 @@
using GerbilManagerWebAPI.Models;
using GerbilManagerWebAPI.Repositories;
using GerbilManagerWebAPI.DAL;
using GerbilManagerWebAPI.Dtos;
using Microsoft.AspNetCore.Mvc;
@@ -11,20 +11,23 @@ namespace GerbilManagerWebAPI.Controllers
[Route("gerbils")]
public class GerbilsController : ControllerBase
{
private readonly IGerbilRepository repository;
private readonly IBreederRepository breederRepository;
private readonly ILitterRepository litterRepository;
private readonly UnitOfWork unitOfWork;
public GerbilsController(IGerbilRepository repo)
public GerbilsController(UnitOfWork unitOfWork)
{
this.repository = repo;
this.unitOfWork = unitOfWork;
}
//GET /gerbils
[HttpGet]
public IEnumerable<GerbilDto> GetGerbils()
public IEnumerable<GerbilDto> GetGerbils([FromQuery]string? name)
{
var items = repository.GetGerbils().Select( gerbil => gerbil.AsDto());
if(name != null)
{
return unitOfWork.GerbilRepository.Get(filter: x => x.Name == name).Select( gerbil => gerbil.AsDto());
}
var items = unitOfWork.GerbilRepository.Get().Select( gerbil => gerbil.AsDto());
return items;
}
@@ -32,7 +35,7 @@ namespace GerbilManagerWebAPI.Controllers
[HttpGet("{id}")]
public ActionResult<GerbilDto> GetGerbil(Guid id)
{
var item = repository.GetGerbil(id).AsDto();
var item = unitOfWork.GerbilRepository.GetByID(id).AsDto();
if(item is null)
{
@@ -52,15 +55,16 @@ namespace GerbilManagerWebAPI.Controllers
if(dto.Litter != Guid.Empty)
{
gerbil.Litter = litterRepository.GetLitter(dto.Litter);
gerbil.Litter = unitOfWork.LitterRepository.GetByID(dto.Litter);
}
if(dto.Breeder != Guid.Empty)
{
gerbil.Breeder = breederRepository.GetBreeder(dto.Breeder);
gerbil.Breeder = unitOfWork.BreederRepository.GetByID(dto.Breeder);
}
this.repository.CreateGerbil(gerbil);
this.unitOfWork.GerbilRepository.Insert(gerbil);
this.unitOfWork.Save();
return CreatedAtAction(nameof(GetGerbil), new { id = gerbil.Id}, gerbil.AsDto());
}
@@ -68,34 +72,37 @@ namespace GerbilManagerWebAPI.Controllers
[HttpPut("{id}")]
public ActionResult UpdateGerbil(Guid id, UpdateGerbilDto gerbilDto)
{
var existingItem = repository.GetGerbil(id);
var existingItem = unitOfWork.GerbilRepository.GetByID(id);
if(existingItem is null)
{
return NotFound();
}
Gerbil updatedGerbil = existingItem with{
Breeder = breederRepository.GetBreeder(gerbilDto.Breeder.Id),
Litter = litterRepository.GetLitter(gerbilDto.Litter.Id),
Gender = (Gender) (int) gerbilDto.Gender,
Name = gerbilDto.Name
Breeder = unitOfWork.BreederRepository.GetByID(gerbilDto?.Breeder ?? Guid.Empty),
Litter = unitOfWork.LitterRepository.GetByID(gerbilDto?.Litter ?? Guid.Empty),
Gender = (Gender) (int) gerbilDto?.Gender!,
Name = gerbilDto?.Name!
};
repository.UpdateGerbil(updatedGerbil);
unitOfWork.GerbilRepository.Update(updatedGerbil);
this.unitOfWork.Save();
return NoContent();
}
[HttpDelete("{id}")]
public ActionResult DeleteGerbil(Guid id)
{
var existingGerbil = repository.GetGerbil(id);
var existingGerbil = unitOfWork.GerbilRepository.GetByID(id);
if(existingGerbil is null)
{
return NotFound();
}
repository.DeleteGerbil(existingGerbil);
unitOfWork.GerbilRepository.Delete(existingGerbil);
this.unitOfWork.Save();
return NoContent();
}

View File

@@ -1,5 +1,5 @@
using GerbilManagerWebAPI.Models;
using GerbilManagerWebAPI.Repositories;
using GerbilManagerWebAPI.DAL;
using GerbilManagerWebAPI.Dtos;
using GerbilManagerWebAPI.Converter;
@@ -13,20 +13,18 @@ namespace GerbilManagerWebAPI.Controllers
[Route("litters")]
public class LittersController : ControllerBase
{
private readonly IGerbilRepository gerbilrepository;
private readonly IBreederRepository breederRepository;
private readonly ILitterRepository repository;
private readonly UnitOfWork unitOfWork;
public LittersController(ILitterRepository repo)
public LittersController(UnitOfWork unitOfWork)
{
this.repository = repo;
this.unitOfWork = unitOfWork;
}
//GET /gerbils
[HttpGet]
public IEnumerable<LitterDto> GetLitters()
{
var items = repository.GetLitters().Select( litter => litter.AsDto());
var items = unitOfWork.LitterRepository.Get(includeProperties: "Father,Mother").Select( litter => litter.AsDto());
return items;
}
@@ -34,7 +32,7 @@ namespace GerbilManagerWebAPI.Controllers
[HttpGet("{id}")]
public ActionResult<LitterDto> GetLitter(Guid id)
{
var item = repository.GetLitter(id).AsDto();
var item = unitOfWork.LitterRepository.GetByID(id).AsDto();
if(item is null)
{
@@ -46,16 +44,25 @@ namespace GerbilManagerWebAPI.Controllers
[HttpPost()]
public ActionResult<LitterDto> CreateLitter(CreateLitterDto dto)
{
//Sanity check if father is male and mother is female
var father = this.unitOfWork.GerbilRepository.GetByID(dto.Father ?? Guid.Empty);
var mother = this.unitOfWork.GerbilRepository.GetByID(dto.Mother ?? Guid.Empty);
if(father?.Gender == Gender.female || mother?.Gender == Gender.male)
{
return BadRequest($"The mothers gender is {mother?.Gender.ToString() ?? "unknown"} and the fathers {father?.Gender.ToString() ?? "unknown"}.");
}
Litter litter = new Litter{
Id = Guid.NewGuid(),
Date = dto.Date,
Father = gerbilrepository.GetGerbil(dto.Father ?? Guid.Empty),
Mother = gerbilrepository.GetGerbil(dto.Mother ?? Guid.Empty),
Father = this.unitOfWork.GerbilRepository.GetByID(dto.Father ?? Guid.Empty),
Mother = this.unitOfWork.GerbilRepository.GetByID(dto.Mother ?? Guid.Empty),
Name = dto.Name,
Strength = dto.Strength
};
this.repository.CreateLitter(litter);
this.unitOfWork.LitterRepository.Insert(litter);
this.unitOfWork.Save();
return CreatedAtAction(nameof(GetLitter), new { id = litter.Id}, litter.AsDto());
}
@@ -63,7 +70,7 @@ namespace GerbilManagerWebAPI.Controllers
[HttpPut("{id}")]
public ActionResult UpdateLitter(Guid id, UpdateLitterDto litterDto)
{
var existingItem = repository.GetLitter(id);
var existingItem = unitOfWork.LitterRepository.GetByID(id);
if(existingItem is null)
{
return NotFound();
@@ -72,26 +79,29 @@ namespace GerbilManagerWebAPI.Controllers
Litter updatedLitter = existingItem with{
Date = litterDto.Date ?? DateTime.Now,
Strength = litterDto.Strength,
Father = gerbilrepository.GetGerbil(litterDto.Father ?? Guid.Empty),
Mother = gerbilrepository.GetGerbil(litterDto.Mother ?? Guid.Empty),
Name = litterDto.Name
Father = this.unitOfWork.GerbilRepository.GetByID(litterDto.Father ?? Guid.Empty),
Mother = this.unitOfWork.GerbilRepository.GetByID(litterDto.Mother ?? Guid.Empty),
Name = litterDto?.Name!
};
repository.UpdateLitter(updatedLitter);
unitOfWork.LitterRepository.Update(updatedLitter);
this.unitOfWork.Save();
return NoContent();
}
[HttpDelete("{id}")]
public ActionResult DeleteLitter(Guid id)
{
var existingLitter = repository.GetLitter(id);
var existingLitter = unitOfWork.LitterRepository.GetByID(id);
if(existingLitter is null)
{
return NotFound();
}
repository.DeleteLitter(existingLitter);
unitOfWork.LitterRepository.Delete(existingLitter);
this.unitOfWork.Save();
return NoContent();
}

View File

@@ -0,0 +1,77 @@
using System.Linq.Expressions;
using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace GerbilManagerWebAPI.DAL
{
public class GenericRepository<TEntity> where TEntity : class
{
internal ApplicationContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(ApplicationContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null!,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null!,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet?.Find(id)!;
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet?.Find(id)!;
if (entityToDelete is not null) Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
}

View File

@@ -0,0 +1,76 @@
using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace GerbilManagerWebAPI.DAL
{
public class UnitOfWork : IDisposable
{
private ApplicationContext context;
private GenericRepository<Gerbil> gerbilRepository = null!;
private GenericRepository<Litter> litterRepository = null!;
private GenericRepository<Breeder> breederRepository = null!;
public GenericRepository<Gerbil> GerbilRepository
{
get
{
if (this.gerbilRepository == null)
{
this.gerbilRepository = new GenericRepository<Gerbil>(context);
}
return gerbilRepository;
}
}
public GenericRepository<Litter> LitterRepository
{
get
{
if (this.litterRepository == null)
{
this.litterRepository = new GenericRepository<Litter>(context);
}
return litterRepository;
}
}
public GenericRepository<Breeder> BreederRepository
{
get
{
if (this.breederRepository == null)
{
this.breederRepository = new GenericRepository<Breeder>(context);
}
return breederRepository;
}
}
public UnitOfWork(ApplicationContext context) => this.context = context;
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -4,7 +4,7 @@ namespace GerbilManagerWebAPI.Dtos
{
public string? Name { get; init; }
public GenderDto? Gender { get; init; }
public LitterDto? Litter { get; init; }
public BreederDto? Breeder { get; init; }
public Guid? Litter { get; init; }
public Guid? Breeder { get; init; }
}
}

View File

@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace GerbilManagerWebAPI.Migrations
{
[DbContext(typeof(ApplicationContext))]
[Migration("20231023182219_InitialCreate")]
partial class InitialCreate
[Migration("20231030203950_initialCreate")]
partial class initialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -31,11 +31,12 @@ namespace GerbilManagerWebAPI.Migrations
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Breeders");
b.ToTable("Breeders", (string)null);
});
modelBuilder.Entity("GerbilManagerWebAPI.Models.Gerbil", b =>
@@ -47,13 +48,15 @@ namespace GerbilManagerWebAPI.Migrations
b.Property<Guid?>("BreederId")
.HasColumnType("uniqueidentifier");
b.Property<int>("Gender")
.HasColumnType("int");
b.Property<string>("Gender")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("LitterId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
@@ -62,7 +65,7 @@ namespace GerbilManagerWebAPI.Migrations
b.HasIndex("LitterId");
b.ToTable("Gerbils");
b.ToTable("Gerbils", (string)null);
});
modelBuilder.Entity("GerbilManagerWebAPI.Models.Litter", b =>
@@ -81,9 +84,10 @@ namespace GerbilManagerWebAPI.Migrations
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Strength")
b.Property<int?>("Strength")
.HasColumnType("int");
b.HasKey("Id");
@@ -92,7 +96,7 @@ namespace GerbilManagerWebAPI.Migrations
b.HasIndex("MotherId");
b.ToTable("Litters");
b.ToTable("Litters", (string)null);
});
modelBuilder.Entity("GerbilManagerWebAPI.Models.Gerbil", b =>

View File

@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace GerbilManagerWebAPI.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
public partial class initialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -16,7 +16,7 @@ namespace GerbilManagerWebAPI.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true)
Name = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
@@ -28,8 +28,8 @@ namespace GerbilManagerWebAPI.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Gender = table.Column<int>(type: "nvarchar(max)", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Gender = table.Column<string>(type: "nvarchar(max)", nullable: false),
LitterId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
BreederId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},
@@ -48,9 +48,9 @@ namespace GerbilManagerWebAPI.Migrations
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Date = table.Column<DateTime>(type: "datetime2", nullable: false),
Strength = table.Column<int>(type: "int", nullable: false),
Strength = table.Column<int>(type: "int", nullable: true),
FatherId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
MotherId = table.Column<Guid>(type: "uniqueidentifier", nullable: true)
},

View File

@@ -28,11 +28,12 @@ namespace GerbilManagerWebAPI.Migrations
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Breeders");
b.ToTable("Breeders", (string)null);
});
modelBuilder.Entity("GerbilManagerWebAPI.Models.Gerbil", b =>
@@ -44,13 +45,15 @@ namespace GerbilManagerWebAPI.Migrations
b.Property<Guid?>("BreederId")
.HasColumnType("uniqueidentifier");
b.Property<int>("Gender")
.HasColumnType("int");
b.Property<string>("Gender")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("LitterId")
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
@@ -59,7 +62,7 @@ namespace GerbilManagerWebAPI.Migrations
b.HasIndex("LitterId");
b.ToTable("Gerbils");
b.ToTable("Gerbils", (string)null);
});
modelBuilder.Entity("GerbilManagerWebAPI.Models.Litter", b =>
@@ -78,9 +81,10 @@ namespace GerbilManagerWebAPI.Migrations
.HasColumnType("uniqueidentifier");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Strength")
b.Property<int?>("Strength")
.HasColumnType("int");
b.HasKey("Id");
@@ -89,7 +93,7 @@ namespace GerbilManagerWebAPI.Migrations
b.HasIndex("MotherId");
b.ToTable("Litters");
b.ToTable("Litters", (string)null);
});
modelBuilder.Entity("GerbilManagerWebAPI.Models.Gerbil", b =>

View File

@@ -1,4 +1,4 @@
using GerbilManagerWebAPI.Repositories;
using GerbilManagerWebAPI.DAL;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
@@ -10,7 +10,7 @@ builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<ApplicationContext>(opts => opts.UseSqlServer(builder.Configuration.GetConnectionString("sqlConnection")));
builder.Services.AddScoped<IGerbilRepository, EFGerbilRepository>();
builder.Services.AddScoped<UnitOfWork>();
var app = builder.Build();

View File

@@ -1,42 +0,0 @@
using GerbilManagerWebAPI.Models;
using Microsoft.EntityFrameworkCore;
namespace GerbilManagerWebAPI.Repositories
{
public class EFGerbilRepository : IGerbilRepository
{
private readonly ApplicationContext context;
public EFGerbilRepository(ApplicationContext context)
{
this.context = context;
}
public void CreateGerbil(Gerbil gerbil)
{
context.Gerbils.Add(gerbil);
context.SaveChanges();
}
public void DeleteGerbil(Gerbil gerbil)
{
context.Gerbils.Remove(gerbil);
context.SaveChanges();
}
public Gerbil GetGerbil(Guid id)
{
return context.Gerbils.Where(entity => entity.Id == id).First();
}
public IEnumerable<Gerbil> GetGerbils()
{
return context.Gerbils.ToList();
}
public void UpdateGerbil(Gerbil gerbil)
{
context.Gerbils.Update(gerbil);
context.SaveChanges();
}
}
}

View File

@@ -1,17 +0,0 @@
using GerbilManagerWebAPI.Models;
namespace GerbilManagerWebAPI.Repositories
{
public interface IBreederRepository
{
IEnumerable<Breeder> GetBreeders();
Breeder GetBreeder(Guid id);
void CreateBreeder(Breeder breeder);
void UpdateBreeder(Breeder breeder);
void DeleteBreeder(Breeder breeder);
}
}

View File

@@ -1,17 +0,0 @@
using GerbilManagerWebAPI.Models;
namespace GerbilManagerWebAPI.Repositories
{
public interface IGerbilRepository
{
IEnumerable<Gerbil> GetGerbils();
Gerbil GetGerbil(Guid id);
void CreateGerbil(Gerbil gerbil);
void UpdateGerbil(Gerbil gerbil);
void DeleteGerbil(Gerbil gerilb);
}
}

View File

@@ -1,15 +0,0 @@
using GerbilManagerWebAPI.Models;
namespace GerbilManagerWebAPI.Repositories
{
public interface ILitterRepository
{
IEnumerable<Litter> GetLitters();
Litter GetLitter(Guid id);
void CreateLitter(Litter litter);
void UpdateLitter(Litter litter);
void DeleteLitter(Litter litter);
}
}

View File

@@ -1,39 +0,0 @@
using GerbilManagerWebAPI.Models;
namespace GerbilManagerWebAPI.Repositories
{
public class InMemGerbilRepository : IGerbilRepository
{
private readonly List<Gerbil> _gerbilList = new()
{
new Gerbil() {Id = Guid.NewGuid(), Name = "Blacky", Breeder = null, Litter = null, Gender = Gender.unknown},
new Gerbil() {Id = Guid.NewGuid(), Name = "Coocky", Breeder = null, Litter = null, Gender = Gender.male},
new Gerbil() {Id = Guid.NewGuid(), Name = "Lazy", Breeder = null, Litter = null, Gender = Gender.female}
};
public IEnumerable<Gerbil> GetGerbils()
{
return _gerbilList;
}
public Gerbil GetGerbil(Guid id) => _gerbilList.SingleOrDefault(x => x.Id == id);
public void CreateGerbil(Gerbil gerbil)
{
_gerbilList.Add(gerbil);
}
public void UpdateGerbil(Gerbil gerbil)
{
var index = this._gerbilList.FindIndex(existingGerbil => existingGerbil.Id == gerbil.Id);
_gerbilList[index] = gerbil;
}
public void DeleteGerbil(Gerbil gerbil)
{
var index = this._gerbilList.FindIndex(existingGerbil => existingGerbil.Id == gerbil.Id);
_gerbilList.RemoveAt(index);
}
}
}

View File

@@ -11,6 +11,10 @@ dotnet ef database update
## Create an migration script
dotnet ef migrations add <name>
## Docker
### force compose rebuild
docker-compose build --no-cache
TODO:
- Backup docker volume database

View File

@@ -4,7 +4,17 @@ version: '3.4'
services:
# frontend:
frontend:
image: gerbilmanagernextjs
build:
context: .
dockerfile: gerbil-manager-nextjs/Dockerfile
ports:
- 3000:3000
networks:
- net1
depends_on:
- backend
backend:
image: gerbilmanagerwebapi

View File

@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

36
gerbil-manager-nextjs/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@@ -0,0 +1,63 @@
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY gerbil-manager-nextjs/package.json gerbil-manager-nextjs/package-lock.json* ./
RUN \
if [ -f package-lock.json ]; then npm ci; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY gerbil-manager-nextjs/ .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1
# RUN yarn build
# If using npm comment out above and use below instead
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]

View File

@@ -0,0 +1,40 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
## Inspiration
[SideBar](https://tailwindcomponents.com/component/sub-menu-sidebar)

View File

@@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
module.exports = {
output: 'standalone'
}

4356
gerbil-manager-nextjs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
{
"name": "gerbil-manager-nextjs",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "^18",
"react-dom": "^18",
"next": "14.0.1"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"eslint": "^8",
"eslint-config-next": "14.0.1"
}
}

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,27 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}

View File

@@ -0,0 +1,22 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,121 @@
import Image from 'next/image'
export default function Home() {
return (
<aside className="flex">
<div className="flex flex-col items-center w-16 h-screen py-8 space-y-8 bg-white dark:bg-gray-900 dark:border-gray-700">
<a href="#">
</a>
<a href="#" className="p-1.5 text-gray-500 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-400 dark:hover:bg-gray-800 hover:bg-gray-100">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
</svg>
</a>
<a href="#" className="p-1.5 text-blue-500 transition-colors duration-200 bg-blue-100 rounded-lg dark:text-blue-400 dark:bg-gray-800">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
</svg>
</a>
<a href="#" className="p-1.5 text-gray-500 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-400 dark:hover:bg-gray-800 hover:bg-gray-100">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 6a7.5 7.5 0 107.5 7.5h-7.5V6z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 10.5H21A7.5 7.5 0 0013.5 3v7.5z" />
</svg>
</a>
<a href="#" className="p-1.5 text-gray-500 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-400 dark:hover:bg-gray-800 hover:bg-gray-100">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
</svg>
</a>
<a href="#" className="p-1.5 text-gray-500 focus:outline-nones transition-colors duration-200 rounded-lg dark:text-gray-400 dark:hover:bg-gray-800 hover:bg-gray-100">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</a>
</div>
<div className="h-screen py-8 overflow-y-auto bg-white border-l border-r sm:w-64 w-60 dark:bg-gray-900 dark:border-gray-700">
<h2 className="px-5 text-lg font-medium text-gray-800 dark:text-white">Accounts</h2>
<div className="mt-8 space-y-4">
<button className="flex items-center w-full px-5 py-2 transition-colors duration-200 dark:hover:bg-gray-800 gap-x-2 hover:bg-gray-100 focus:outline-none">
<div className="text-left rtl:text-right">
<h1 className="text-sm font-medium text-gray-700 capitalize dark:text-white">Mia John</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">11.2 Followers</p>
</div>
</button>
<button className="flex items-center w-full px-5 py-2 transition-colors duration-200 dark:hover:bg-gray-800 gap-x-2 hover:bg-gray-100 focus:outline-none">
<div className="text-left rtl:text-right">
<h1 className="text-sm font-medium text-gray-700 capitalize dark:text-white">arthur melo</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">1.2 Followers</p>
</div>
</button>
<button className="flex items-center w-full px-5 py-2 transition-colors duration-200 bg-gray-100 dark:bg-gray-800 gap-x-2 focus:outline-none">
<div className="relative">
<span className="h-2 w-2 rounded-full bg-emerald-500 absolute right-0.5 ring-1 ring-white bottom-0"></span>
</div>
<div className="text-left rtl:text-right">
<h1 className="text-sm font-medium text-gray-700 capitalize dark:text-white">Jane Doe</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">15.6 Followers</p>
</div>
</button>
<button className="flex items-center w-full px-5 py-2 transition-colors duration-200 dark:hover:bg-gray-800 gap-x-2 hover:bg-gray-100 focus:outline-none">
<div className="text-left rtl:text-right">
<h1 className="text-sm font-medium text-gray-700 capitalize dark:text-white">Amelia. Anderson</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">32.9 Followers</p>
</div>
</button>
<button className="flex items-center w-full px-5 py-2 transition-colors duration-200 dark:hover:bg-gray-800 gap-x-2 hover:bg-gray-100 focus:outline-none">
<div className="text-left rtl:text-right">
<h1 className="text-sm font-medium text-gray-700 capitalize dark:text-white">Joseph Gonzalez</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">100.2 Followers</p>
</div>
</button>
<button className="flex items-center w-full px-5 py-2 transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 gap-x-2 focus:outline-none">
<div className="relative">
<span className="h-2 w-2 rounded-full bg-emerald-500 absolute right-0.5 ring-1 ring-white bottom-0"></span>
</div>
<div className="text-left rtl:text-right">
<h1 className="text-sm font-medium text-gray-700 capitalize dark:text-white">Olivia Wathan</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">8.6 Followers</p>
</div>
</button>
<button className="flex items-center w-full px-5 py-2 transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-800 gap-x-2 focus:outline-none">
<div className="relative">
<span className="h-2 w-2 rounded-full bg-emerald-500 absolute right-0.5 ring-1 ring-white bottom-0"></span>
</div>
<div className="text-left rtl:text-right">
<h1 className="text-sm font-medium text-gray-700 capitalize dark:text-white">Junior REIS</h1>
<p className="text-xs text-gray-500 dark:text-gray-400">56.6 Followers</p>
</div>
</button>
</div>
</div>
</aside>
)
}

View File

@@ -0,0 +1,20 @@
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
},
},
plugins: [],
}
export default config

View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}