diff --git a/Cloud/ApplicationContext.cs b/Cloud/ApplicationContext.cs index e91cc00..6a60cf1 100644 --- a/Cloud/ApplicationContext.cs +++ b/Cloud/ApplicationContext.cs @@ -4,7 +4,8 @@ using Microsoft.EntityFrameworkCore; namespace Cloud; public class ApplicationContext : DbContext { - public DbSet Users { get; set; } + public DbSet Users { get; set; } = null!; + public DbSet Farms { get; set; } = null!; public ApplicationContext(DbContextOptions options) : base(options) diff --git a/Cloud/Controllers/FarmController.cs b/Cloud/Controllers/FarmController.cs new file mode 100644 index 0000000..a1e7b73 --- /dev/null +++ b/Cloud/Controllers/FarmController.cs @@ -0,0 +1,128 @@ +using Cloud.Models; +using Cloud.Requests; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Cloud.Controllers +{ + [Authorize] + [ApiController] + [Route("api/user")] + public class FarmController : ControllerBase + { + private IConfiguration _config; + private ApplicationContext _context; + + public FarmController(IConfiguration config, ApplicationContext context) + { + _config = config; + _context = context; + } + + [HttpGet("{userId}/farm")] + public async Task>> Index (int userId) + { + try + { + List farms = await + _context.Farms.Where(x => x.UserId == userId).AsNoTracking().ToListAsync(); + if (!farms.Any()) + return NotFound("Farms is not found"); + + return Ok(farms); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpGet("{userId}/farm/{farmId}")] + public async Task> Show(int userId, int farmId) + { + try + { + Farm? farm = await + _context.Farms.FirstOrDefaultAsync(x => x.UserId == userId && x.Id == farmId); + + if (farm == null) + return NotFound("Farm is not found"); + + return Ok(farm); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPost("{userId}/farm")] + public async Task> Create([FromBody] FarmRequest farmRequest, int userId) + { + try + { + var farm = new Farm { + Name = farmRequest.Name, + UserId = userId, + RaspberryMacAddr = farmRequest.RaspberryMacAddr, + }; + + Farm? farmCreated = _context.Farms.Add(farm).Entity; + await _context.SaveChangesAsync(); + + return Ok(farmCreated); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPut("{userId}/farm/{farmId}")] + public async Task> Update([FromBody] FarmRequest farmRequest, int userId, int farmId) + { + try + { + Farm? farm = await _context.Farms.FirstOrDefaultAsync(x => x.Id == farmId && x.UserId == userId); + + if (farm == null) + return NotFound("Farm is not found"); + + farm.Name = farmRequest.Name; + farm.RaspberryMacAddr = farmRequest.RaspberryMacAddr; + + _context.Farms.Update(farm); + await _context.SaveChangesAsync(); + + return Ok(farm); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("{userId}/farm/{farmId}")] + public async Task Delete(int userId, int farmId) + { + try + { + Farm? farm = await _context.Farms.FirstOrDefaultAsync(x => x.Id == farmId && x.UserId == userId); + + if (farm == null) + return NotFound("Farm is not found"); + + _context.Farms.Remove(farm); + await _context.SaveChangesAsync(); + + return Ok("Farm deleted successfully"); + } + catch (Exception ex) + { + return BadRequest(ex.Message); + } + } + + } +} diff --git a/Cloud/Migrations/20241027220558_CreateUsersTable.Designer.cs b/Cloud/Migrations/20241027220558_CreateUsersTable.Designer.cs index b021b5d..d1c7360 100644 --- a/Cloud/Migrations/20241027220558_CreateUsersTable.Designer.cs +++ b/Cloud/Migrations/20241027220558_CreateUsersTable.Designer.cs @@ -10,44 +10,44 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Cloud.Migrations { - [DbContext(typeof(ApplicationContext))] - [Migration("20241027220558_CreateUsersTable")] - partial class CreateUsersTable - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { + [DbContext(typeof(ApplicationContext))] + [Migration("20241027220558_CreateUsersTable")] + partial class CreateUsersTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.14") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + modelBuilder + .HasAnnotation("ProductVersion", "6.0.14") + .HasAnnotation("Relational:MaxIdentifierLength", 63); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Cloud.Models.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); + modelBuilder.Entity("Cloud.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Email") - .IsRequired() - .HasColumnType("text"); + b.Property("Email") + .IsRequired() + .HasColumnType("text"); - b.Property("Name") - .IsRequired() - .HasColumnType("text"); + b.Property("Name") + .IsRequired() + .HasColumnType("text"); - b.Property("Password") - .IsRequired() - .HasColumnType("text"); + b.Property("Password") + .IsRequired() + .HasColumnType("text"); - b.HasKey("Id"); + b.HasKey("Id"); - b.ToTable("Users"); - }); + b.ToTable("Users"); + }); #pragma warning restore 612, 618 - } - } + } + } } diff --git a/Cloud/Migrations/20241027220558_CreateUsersTable.cs b/Cloud/Migrations/20241027220558_CreateUsersTable.cs index 65e5500..4c9a4dd 100644 --- a/Cloud/Migrations/20241027220558_CreateUsersTable.cs +++ b/Cloud/Migrations/20241027220558_CreateUsersTable.cs @@ -5,30 +5,30 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Cloud.Migrations { - public partial class CreateUsersTable : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Name = table.Column(type: "text", nullable: false), - Email = table.Column(type: "text", nullable: false), - Password = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - } + public partial class CreateUsersTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false), + Password = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Users"); - } - } + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } } diff --git a/Cloud/Migrations/20241028192806_CreateFarmsTable.Designer.cs b/Cloud/Migrations/20241028192806_CreateFarmsTable.Designer.cs new file mode 100644 index 0000000..4bc7f80 --- /dev/null +++ b/Cloud/Migrations/20241028192806_CreateFarmsTable.Designer.cs @@ -0,0 +1,95 @@ +// +using Cloud; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Cloud.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20241028192806_CreateFarmsTable")] + partial class CreateFarmsTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.14") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Cloud.Models.Farm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RaspberryMacAddr") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Farms"); + }); + + modelBuilder.Entity("Cloud.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Cloud.Models.Farm", b => + { + b.HasOne("Cloud.Models.User", "User") + .WithMany("Farms") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Cloud.Models.User", b => + { + b.Navigation("Farms"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Cloud/Migrations/20241028192806_CreateFarmsTable.cs b/Cloud/Migrations/20241028192806_CreateFarmsTable.cs new file mode 100644 index 0000000..a297728 --- /dev/null +++ b/Cloud/Migrations/20241028192806_CreateFarmsTable.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Cloud.Migrations +{ + public partial class CreateFarmsTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Farms", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + UserId = table.Column(type: "integer", nullable: false), + RaspberryMacAddr = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Farms", x => x.Id); + table.ForeignKey( + name: "FK_Farms_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Farms_UserId", + table: "Farms", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Farms"); + } + } +} diff --git a/Cloud/Migrations/ApplicationContextModelSnapshot.cs b/Cloud/Migrations/ApplicationContextModelSnapshot.cs index fc15a07..c499a41 100644 --- a/Cloud/Migrations/ApplicationContextModelSnapshot.cs +++ b/Cloud/Migrations/ApplicationContextModelSnapshot.cs @@ -21,6 +21,32 @@ namespace Cloud.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Cloud.Models.Farm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("RaspberryMacAddr") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Farms"); + }); + modelBuilder.Entity("Cloud.Models.User", b => { b.Property("Id") @@ -45,6 +71,22 @@ namespace Cloud.Migrations b.ToTable("Users"); }); + + modelBuilder.Entity("Cloud.Models.Farm", b => + { + b.HasOne("Cloud.Models.User", "User") + .WithMany("Farms") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Cloud.Models.User", b => + { + b.Navigation("Farms"); + }); #pragma warning restore 612, 618 } } diff --git a/Cloud/Models/Farm.cs b/Cloud/Models/Farm.cs new file mode 100644 index 0000000..a17d3bc --- /dev/null +++ b/Cloud/Models/Farm.cs @@ -0,0 +1,12 @@ +namespace Cloud.Models +{ + public class Farm + { + public int Id { get; set; } + public string Name { get; set; } + public int UserId { get; set; } + public User? User { get; set; } + public string RaspberryMacAddr { get; set; } + + } +} diff --git a/Cloud/Models/User.cs b/Cloud/Models/User.cs index 27c8ee4..269dec4 100644 --- a/Cloud/Models/User.cs +++ b/Cloud/Models/User.cs @@ -8,4 +8,6 @@ public class User public string Email { get; set; } public string Password { get; set; } + + public List Farms { get; set; } = new(); } \ No newline at end of file diff --git a/Cloud/Program.cs b/Cloud/Program.cs index 1c10d82..6f77533 100644 --- a/Cloud/Program.cs +++ b/Cloud/Program.cs @@ -38,6 +38,7 @@ builder.Services.AddFluentValidationAutoValidation(); builder.Services.AddFluentValidationClientsideAdapters(); builder.Services.AddValidatorsFromAssemblyContaining(); builder.Services.AddValidatorsFromAssemblyContaining(); +builder.Services.AddValidatorsFromAssemblyContaining(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/Cloud/Requests/FarmRequest.cs b/Cloud/Requests/FarmRequest.cs new file mode 100644 index 0000000..c032dfa --- /dev/null +++ b/Cloud/Requests/FarmRequest.cs @@ -0,0 +1,8 @@ +namespace Cloud.Requests +{ + public class FarmRequest + { + public string Name { get; set; } + public string RaspberryMacAddr { get; set; } + } +} diff --git a/Cloud/Validation/FarmValidator.cs b/Cloud/Validation/FarmValidator.cs new file mode 100644 index 0000000..4c262fb --- /dev/null +++ b/Cloud/Validation/FarmValidator.cs @@ -0,0 +1,18 @@ +using Cloud.Requests; +using FluentValidation; + +namespace Cloud.Validation +{ + public class FarmValidator : AbstractValidator + { + public FarmValidator() + { + RuleFor(request => request.RaspberryMacAddr) + .NotEmpty().WithMessage("MAC address can't be empty") + .Matches("^([0-9A-Fa-f]{2}[:-]?){5}([0-9A-Fa-f]{2})$").WithMessage("MAC address is not valid"); + + RuleFor(request => request.Name) + .NotEmpty().WithMessage("Name can't be empty"); + } + } +}