diff --git a/src/Portfolio.Api/App.razor b/src/Portfolio.Api/App.razor
new file mode 100644
index 0000000..4124adc
--- /dev/null
+++ b/src/Portfolio.Api/App.razor
@@ -0,0 +1,58 @@
+@using Microsoft.AspNetCore.Components.Web;
+@using Microsoft.AspNetCore.Components.Routing
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Portfolio.Api/DatabaseContext.cs b/src/Portfolio.Api/DatabaseContext.cs
new file mode 100644
index 0000000..2d46736
--- /dev/null
+++ b/src/Portfolio.Api/DatabaseContext.cs
@@ -0,0 +1,25 @@
+using Microsoft.EntityFrameworkCore;
+using Portfolio.Shared.Models;
+
+namespace Portfolio.Api;
+
+public class DatabaseContext(DbContextOptions options) : DbContext(options) {
+
+ public DbSet Projects { get; set; }
+
+ public DbSet Languages { get; set; }
+
+ public DbSet Technologies { get; set; }
+
+ public DbSet Timeline { get; set; }
+
+ public DbSet About { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder) {
+ modelBuilder.Entity()
+ .HasMany(p => p.Languages)
+ .WithMany()
+ .UsingEntity("ProjectLanguage");
+ }
+
+}
\ No newline at end of file
diff --git a/src/Portfolio.Api/Portfolio.Api.csproj b/src/Portfolio.Api/Portfolio.Api.csproj
index 35db154..f7751ce 100644
--- a/src/Portfolio.Api/Portfolio.Api.csproj
+++ b/src/Portfolio.Api/Portfolio.Api.csproj
@@ -8,6 +8,8 @@
+
+
diff --git a/src/Portfolio.Api/Program.cs b/src/Portfolio.Api/Program.cs
index e850aef..39a8398 100644
--- a/src/Portfolio.Api/Program.cs
+++ b/src/Portfolio.Api/Program.cs
@@ -1,3 +1,6 @@
+using HopFrame.Web;
+using Portfolio.Api;
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
@@ -5,6 +8,15 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.AddServiceDefaults();
+builder.AddNpgsqlDbContext("data");
+
+builder.Services.AddHopFrame(options => {
+ options.DisplayUserInfo(false);
+ options.AddDbContext();
+});
+builder.Services.AddRazorComponents()
+ .AddInteractiveServerComponents();
+
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -12,7 +24,17 @@ if (app.Environment.IsDevelopment()) {
app.MapOpenApi();
}
+await using (var scope = app.Services.CreateAsyncScope()) {
+ var db = scope.ServiceProvider.GetRequiredService();
+ await db.Database.EnsureCreatedAsync();
+}
+
app.UseHttpsRedirection();
app.MapDefaultEndpoints();
+app.UseAntiforgery();
+app.MapStaticAssets();
+app.MapRazorComponents()
+ .MapHopFramePages();
+
app.Run();
diff --git a/src/Portfolio.Host/Portfolio.Host.csproj b/src/Portfolio.Host/Portfolio.Host.csproj
index 5f44a98..083deb5 100644
--- a/src/Portfolio.Host/Portfolio.Host.csproj
+++ b/src/Portfolio.Host/Portfolio.Host.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/Portfolio.Host/Program.cs b/src/Portfolio.Host/Program.cs
index e976287..ae405b9 100644
--- a/src/Portfolio.Host/Program.cs
+++ b/src/Portfolio.Host/Program.cs
@@ -1,9 +1,17 @@
var builder = DistributedApplication.CreateBuilder(args);
-var api = builder.AddProject("api");
+var postgres = builder.AddPostgres("postgres")
+ .WithDataVolume("portfolio-postgres");
+
+var db = postgres.AddDatabase("data");
+
+var api = builder.AddProject("api")
+ .WithReference(db)
+ .WaitFor(db);
builder.AddProject("web")
.WithReference(api)
- .WaitFor(api);
+ .WaitFor(api)
+ .WithExternalHttpEndpoints();
builder.Build().Run();
\ No newline at end of file
diff --git a/src/Portfolio.Shared/Models/About.cs b/src/Portfolio.Shared/Models/About.cs
new file mode 100644
index 0000000..b17cc31
--- /dev/null
+++ b/src/Portfolio.Shared/Models/About.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Portfolio.Shared.Models;
+
+public class About {
+
+ [Key]
+ public int Id { get; private set; } = 0;
+
+ [MaxLength(5000)]
+ public required string AboutMe { get; set; }
+
+ [MaxLength(5000)]
+ public required string Future { get; set; }
+
+}
\ No newline at end of file
diff --git a/src/Portfolio.Shared/Models/Language.cs b/src/Portfolio.Shared/Models/Language.cs
new file mode 100644
index 0000000..f6ee861
--- /dev/null
+++ b/src/Portfolio.Shared/Models/Language.cs
@@ -0,0 +1,20 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Portfolio.Shared.Models;
+
+public sealed class Language {
+
+ [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; init; }
+
+ [MaxLength(255)]
+ public required string Label { get; set; }
+
+ [MaxLength(255)]
+ public required string Identifier { get; set; }
+
+ [MaxLength(255)]
+ public string? Suffix { get; set; }
+
+}
\ No newline at end of file
diff --git a/src/Portfolio.Shared/Models/Project.cs b/src/Portfolio.Shared/Models/Project.cs
new file mode 100644
index 0000000..fab23ce
--- /dev/null
+++ b/src/Portfolio.Shared/Models/Project.cs
@@ -0,0 +1,41 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Portfolio.Shared.Models;
+
+public sealed class Project {
+
+ [Key]
+ public Guid Id { get; init; } = Guid.CreateVersion7();
+
+ [MaxLength(255)]
+ public required string Cover { get; set; }
+
+ [MaxLength(255)]
+ public required string Name { get; set; }
+
+ [MaxLength(1000)]
+ public string? Description { get; set; }
+
+ [MaxLength(255)]
+ public required string SourceCode { get; set; }
+
+ public bool Featured { get; set; }
+
+ public int OrderIndex { get; set; }
+
+ public ProjectStatus Status { get; set; } = ProjectStatus.Finished;
+
+ [ForeignKey("languages")]
+ public List Languages { get; init; } = new();
+
+ public DateTime Created { get; init; } = DateTime.Now;
+
+}
+
+public enum ProjectStatus : byte {
+ Finished = 0,
+ Canceled = 1,
+ Paused = 2,
+ Development = 3
+}
diff --git a/src/Portfolio.Shared/Models/Technology.cs b/src/Portfolio.Shared/Models/Technology.cs
new file mode 100644
index 0000000..edd44dd
--- /dev/null
+++ b/src/Portfolio.Shared/Models/Technology.cs
@@ -0,0 +1,22 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Portfolio.Shared.Models;
+
+public sealed class Technology {
+
+ [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; init; }
+
+ [MaxLength(255)]
+ public required string Name { get; set; }
+
+ public TechnologyLevel Level { get; set; } = TechnologyLevel.Beginner;
+
+}
+
+public enum TechnologyLevel : byte {
+ Beginner = 0,
+ Intermediate = 1,
+ Professional = 2
+}
\ No newline at end of file
diff --git a/src/Portfolio.Shared/Models/TimelineEntry.cs b/src/Portfolio.Shared/Models/TimelineEntry.cs
new file mode 100644
index 0000000..f6cb8fa
--- /dev/null
+++ b/src/Portfolio.Shared/Models/TimelineEntry.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Portfolio.Shared.Models;
+
+public class TimelineEntry {
+
+ [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; init; }
+
+ public DateOnly Date { get; init; }
+
+ [MaxLength(1000)]
+ public required string Description { get; set; }
+
+ public bool Featured { get; set; }
+
+ public TimelineEntryType Type { get; init; }
+
+}
+
+public enum TimelineEntryType : byte {
+ Experience = 0,
+ Carrier = 1
+}