diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c0ece96..9dffbfe 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -34,18 +34,26 @@ } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [ - 4200, - 5000, - 5001 - ], - "portsAttributes": { - "5001": { - "protocol": "https" - } + "forwardPorts": [ + 4200, + 5000, + 5001, + 1433 + ], + "portsAttributes": { + "5001": { + "protocol": "https" }, - // postCreateCommand.sh parameters: $1=SA password, $2=dacpac path, $3=sql script(s) path - "postCreateCommand": "bash .devcontainer/mssql/postCreateCommand.sh 'P@ssw0rd' './bin/Debug/' './.devcontainer/mssql/'" + "1433": { + "protocol": "tcp" + } + }, + // postCreateCommand.sh parameters: $1=SA password, $2=dacpac path, $3=sql script(s) path + "containerEnv": { + "SA_PASSWORD": "P@ssw0rd", + "ACCEPT_EULA": "Y" + }, + "postCreateCommand": "bash .devcontainer/mssql/postCreateCommand.sh 'P@ssw0rd' './bin/Debug/' './.devcontainer/mssql/'" // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" } \ No newline at end of file diff --git a/Api/Api.csproj b/Api/Api.csproj index 4dda03a..dd16980 100644 --- a/Api/Api.csproj +++ b/Api/Api.csproj @@ -12,6 +12,11 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + diff --git a/Api/Controllers/ProductController.cs b/Api/Controllers/ProductController.cs new file mode 100644 index 0000000..628f617 --- /dev/null +++ b/Api/Controllers/ProductController.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Api.Models; + +namespace Api.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class ProductController : ControllerBase + { + private readonly AppDbContext _context; + + public ProductController(AppDbContext context) + { + _context = context; + } + + // GET: api/Product + [HttpGet] + public async Task>> GetProducts() + { + return await _context.Products.ToListAsync(); + } + + // POST: api/Product + [HttpPost] + public async Task> PostProduct([FromBody] Product product) + { + _context.Products.Add(product); + await _context.SaveChangesAsync(); + return CreatedAtAction(nameof(GetProducts), new { id = product.Id }, product); + } + + // PUT: api/Product/{id} + [HttpPut("{id}")] + public async Task PutProduct(int id, [FromBody] Product product) + { + if (id != product.Id) + { + return BadRequest(); + } + _context.Entry(product).State = EntityState.Modified; + try + { + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!await _context.Products.AnyAsync(e => e.Id == id)) + { + return NotFound(); + } + else + { + throw; + } + } + return NoContent(); + } + } +} diff --git a/Api/Migrations/20250727154636_InitialCreate.Designer.cs b/Api/Migrations/20250727154636_InitialCreate.Designer.cs new file mode 100644 index 0000000..3ed32b7 --- /dev/null +++ b/Api/Migrations/20250727154636_InitialCreate.Designer.cs @@ -0,0 +1,56 @@ +// +using Api.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250727154636_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Api.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.ToTable("Products"); + + b.HasData( + new + { + Id = 1, + Name = "Sample Product", + Price = 9.99m + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Api/Migrations/20250727154636_InitialCreate.cs b/Api/Migrations/20250727154636_InitialCreate.cs new file mode 100644 index 0000000..f0a0fd5 --- /dev/null +++ b/Api/Migrations/20250727154636_InitialCreate.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Api.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Products", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: true), + Price = table.Column(type: "decimal(18,2)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Products", x => x.Id); + }); + + migrationBuilder.InsertData( + table: "Products", + columns: new[] { "Id", "Name", "Price" }, + values: new object[] { 1, "Sample Product", 9.99m }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Products"); + } + } +} diff --git a/Api/Migrations/AppDbContextModelSnapshot.cs b/Api/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..72262b1 --- /dev/null +++ b/Api/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,53 @@ +// +using Api.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Api.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.ToTable("Products"); + + b.HasData( + new + { + Id = 1, + Name = "Sample Product", + Price = 9.99m + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Api/Models/AppDbContext.cs b/Api/Models/AppDbContext.cs new file mode 100644 index 0000000..4004e93 --- /dev/null +++ b/Api/Models/AppDbContext.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; + +namespace Api.Models +{ + public class Product + { + public int Id { get; set; } + public string? Name { get; set; } + public decimal Price { get; set; } + } + + public class AppDbContext : DbContext + { + public AppDbContext(DbContextOptions options) : base(options) { } + public DbSet Products { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + // Seed data + modelBuilder.Entity().HasData( + new Product { Id = 1, Name = "Sample Product", Price = 9.99M } + ); + } + } +} diff --git a/Api/Program.cs b/Api/Program.cs index 0c98ee1..caf2cd8 100644 --- a/Api/Program.cs +++ b/Api/Program.cs @@ -6,17 +6,19 @@ using Microsoft.Identity.Web.Resource; namespace Api { - public class Program + using Microsoft.EntityFrameworkCore; + using Api.Models; + public static class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. - //builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - // .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")); - builder.Services.AddControllers(); + // Add DbContext with SQL Server + builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -24,19 +26,14 @@ namespace Api var app = builder.Build(); // Configure the HTTP request pipeline. - // if (app.Environment.IsDevelopment()) - // { - app.UseSwagger(); - app.UseSwaggerUI(); - // } + app.UseSwagger(); + app.UseSwaggerUI(); if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } - // app.UseAuthorization(); - app.MapControllers(); app.Run(); diff --git a/Api/appsettings.Development.json b/Api/appsettings.Development.json index ff66ba6..55ad44f 100644 --- a/Api/appsettings.Development.json +++ b/Api/appsettings.Development.json @@ -4,5 +4,8 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ConnectionStrings": { + "DefaultConnection": "Server=localhost,1433;Database=CentrumDb;User=sa;Password=P@ssw0rd;TrustServerCertificate=True;" } }