feat: Phase 2 — Delegated State, Auth, Review-Gate, Notifications, Zombie-Reset
This commit is contained in:
@@ -18,6 +18,7 @@ public enum TaskState
|
||||
{
|
||||
Backlog,
|
||||
InProgress,
|
||||
Delegated,
|
||||
Blocked,
|
||||
Done,
|
||||
Review
|
||||
@@ -29,6 +30,7 @@ public static class TaskStateHelper
|
||||
{
|
||||
[TaskState.Backlog] = "Backlog",
|
||||
[TaskState.InProgress] = "In progress",
|
||||
[TaskState.Delegated] = "Delegated",
|
||||
[TaskState.Blocked] = "Blocked",
|
||||
[TaskState.Done] = "Done",
|
||||
[TaskState.Review] = "Review"
|
||||
@@ -38,6 +40,7 @@ public static class TaskStateHelper
|
||||
{
|
||||
["Backlog"] = TaskState.Backlog,
|
||||
["In progress"] = TaskState.InProgress,
|
||||
["Delegated"] = TaskState.Delegated,
|
||||
["Blocked"] = TaskState.Blocked,
|
||||
["Done"] = TaskState.Done,
|
||||
["Review"] = TaskState.Review
|
||||
@@ -48,13 +51,14 @@ public static class TaskStateHelper
|
||||
{
|
||||
["Backlog"] = "Offen",
|
||||
["In progress"] = "In Bearbeitung",
|
||||
["Delegated"] = "Delegiert",
|
||||
["Review"] = "Review",
|
||||
["Blocked"] = "Blockiert",
|
||||
["Done"] = "Erledigt"
|
||||
};
|
||||
|
||||
/// <summary>Valid task-state string values for API validation.</summary>
|
||||
public static readonly string[] AllStates = ["Backlog", "In progress", "Blocked", "Done", "Review"];
|
||||
public static readonly string[] AllStates = ["Backlog", "In progress", "Delegated", "Blocked", "Done", "Review"];
|
||||
|
||||
/// <summary>Convert a TaskState enum to its API string representation.</summary>
|
||||
public static string ToStateString(this TaskState state) => StateToString[state];
|
||||
@@ -88,6 +92,7 @@ public static class TaskStateHelper
|
||||
{
|
||||
"backlog" => "offen",
|
||||
"in progress" => "inProgress",
|
||||
"delegated" => "delegated",
|
||||
"review" => "review",
|
||||
"blocked" => "blocked",
|
||||
"done" => "done",
|
||||
@@ -104,6 +109,7 @@ public static class TaskStateHelper
|
||||
{
|
||||
"offen" => "Backlog",
|
||||
"inprogress" => "In progress",
|
||||
"delegated" => "Delegated",
|
||||
"review" => "Review",
|
||||
"blocked" => "Blocked",
|
||||
"done" => "Done",
|
||||
@@ -131,15 +137,32 @@ public sealed class WorkTask
|
||||
public string Priority { get; set; } = "Normal";
|
||||
public string Source { get; set; } = "bao";
|
||||
public string? AssignedTo { get; set; }
|
||||
public Guid? ParentTaskId { get; set; }
|
||||
public WorkTask? ParentTask { get; set; }
|
||||
public ICollection<WorkTask> ChildTasks { get; set; } = new List<WorkTask>();
|
||||
public Guid? ProjectId { get; set; }
|
||||
public DateTimeOffset? DueDate { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
public sealed class Notification
|
||||
{
|
||||
public Guid Id { get; init; } = Guid.NewGuid();
|
||||
public required string Type { get; set; } // "task_assigned", "task_review", "task_blocked"
|
||||
public required string Title { get; set; } // "Neue Aufgabe: Memory-Index reparieren"
|
||||
public string? Message { get; set; } // Detailtext
|
||||
public required string ForUser { get; set; } // "bao" oder "iris"
|
||||
public Guid? TaskId { get; set; } // Verknüpfte Task
|
||||
public bool IsRead { get; set; } = false;
|
||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
public sealed class ActivityEvent
|
||||
{
|
||||
public long Id { get; init; }
|
||||
public required string Type { get; set; }
|
||||
public required string Message { get; set; }
|
||||
public Guid? TaskId { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Nexus.Api.Data;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nexus.Api.Migrations
|
||||
{
|
||||
[DbContext(typeof(NexusDbContext))]
|
||||
[Migration("20260618214335_AddNotifications")]
|
||||
partial class AddNotifications
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.8")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.ActivityEvent", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<Guid?>("TaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.ToTable("Activity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.NexusUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<DateTimeOffset?>("LastLoginAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.Notification", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ForUser")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<bool>("IsRead")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<Guid?>("TaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(240)
|
||||
.HasColumnType("character varying(240)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ForUser", "IsRead", "CreatedAt");
|
||||
|
||||
b.ToTable("Notifications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.Project", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(160)
|
||||
.HasColumnType("character varying(160)");
|
||||
|
||||
b.Property<int>("Progress")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Projects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.RefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("FamilyId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<DateTimeOffset?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId", "FamilyId");
|
||||
|
||||
b.ToTable("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AssignedTo")
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Detail")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTimeOffset?>("DueDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("ParentTaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Priority")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("ProjectId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<string>("State")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(240)
|
||||
.HasColumnType("character varying(240)");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AssignedTo");
|
||||
|
||||
b.HasIndex("ParentTaskId");
|
||||
|
||||
b.HasIndex("Source");
|
||||
|
||||
b.ToTable("Tasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.RefreshToken", b =>
|
||||
{
|
||||
b.HasOne("Nexus.Api.Data.NexusUser", "User")
|
||||
.WithMany("RefreshTokens")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.HasOne("Nexus.Api.Data.WorkTask", "ParentTask")
|
||||
.WithMany("ChildTasks")
|
||||
.HasForeignKey("ParentTaskId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("ParentTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.NexusUser", b =>
|
||||
{
|
||||
b.Navigation("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.Navigation("ChildTasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nexus.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddNotifications : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Notifications",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Type = table.Column<string>(type: "character varying(60)", maxLength: 60, nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(240)", maxLength: 240, nullable: false),
|
||||
Message = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
ForUser = table.Column<string>(type: "character varying(60)", maxLength: 60, nullable: false),
|
||||
TaskId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
IsRead = table.Column<bool>(type: "boolean", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Notifications", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Notifications_ForUser_IsRead_CreatedAt",
|
||||
table: "Notifications",
|
||||
columns: new[] { "ForUser", "IsRead", "CreatedAt" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Notifications");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nexus.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddTaskParentChild : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "ParentTaskId",
|
||||
table: "Tasks",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tasks_ParentTaskId",
|
||||
table: "Tasks",
|
||||
column: "ParentTaskId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Tasks_Tasks_ParentTaskId",
|
||||
table: "Tasks",
|
||||
column: "ParentTaskId",
|
||||
principalTable: "Tasks",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Tasks_Tasks_ParentTaskId",
|
||||
table: "Tasks");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Tasks_ParentTaskId",
|
||||
table: "Tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ParentTaskId",
|
||||
table: "Tasks");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nexus.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddTaskDueDate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "DueDate",
|
||||
table: "Tasks",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DueDate",
|
||||
table: "Tasks");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nexus.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddActivityTaskReference : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "TaskId",
|
||||
table: "Activity",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Activity_TaskId",
|
||||
table: "Activity",
|
||||
column: "TaskId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Activity_TaskId",
|
||||
table: "Activity");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TaskId",
|
||||
table: "Activity");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Nexus.Api.Data;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nexus.Api.Migrations
|
||||
{
|
||||
[DbContext(typeof(NexusDbContext))]
|
||||
[Migration("20260618233003_AddDelegatedState")]
|
||||
partial class AddDelegatedState
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.8")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.ActivityEvent", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<Guid?>("TaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.ToTable("Activity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.NexusUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<DateTimeOffset?>("LastLoginAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.Project", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(160)
|
||||
.HasColumnType("character varying(160)");
|
||||
|
||||
b.Property<int>("Progress")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Projects");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.RefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("FamilyId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<DateTimeOffset?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId", "FamilyId");
|
||||
|
||||
b.ToTable("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AssignedTo")
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Detail")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTimeOffset?>("DueDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("ParentTaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Priority")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("ProjectId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Source")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<string>("State")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(240)
|
||||
.HasColumnType("character varying(240)");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AssignedTo");
|
||||
|
||||
b.HasIndex("ParentTaskId");
|
||||
|
||||
b.HasIndex("Source");
|
||||
|
||||
b.ToTable("Tasks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.RefreshToken", b =>
|
||||
{
|
||||
b.HasOne("Nexus.Api.Data.NexusUser", "User")
|
||||
.WithMany("RefreshTokens")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.HasOne("Nexus.Api.Data.WorkTask", "ParentTask")
|
||||
.WithMany("ChildTasks")
|
||||
.HasForeignKey("ParentTaskId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("ParentTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.NexusUser", b =>
|
||||
{
|
||||
b.Navigation("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.Navigation("ChildTasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Nexus.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDelegatedState : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Delegated state is a pure code change to the TaskState enum and
|
||||
// TaskStateHelper. No schema change required since the State column
|
||||
// is already a free-form string column.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// No schema to revert.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,12 +38,19 @@ namespace Nexus.Api.Migrations
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<Guid?>("TaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("TaskId");
|
||||
|
||||
b.ToTable("Activity");
|
||||
});
|
||||
|
||||
@@ -93,6 +100,47 @@ namespace Nexus.Api.Migrations
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.Notification", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ForUser")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<bool>("IsRead")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<Guid?>("TaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(240)
|
||||
.HasColumnType("character varying(240)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ForUser", "IsRead", "CreatedAt");
|
||||
|
||||
b.ToTable("Notifications");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.Project", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -183,6 +231,12 @@ namespace Nexus.Api.Migrations
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTimeOffset?>("DueDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("ParentTaskId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Priority")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
@@ -211,6 +265,8 @@ namespace Nexus.Api.Migrations
|
||||
|
||||
b.HasIndex("AssignedTo");
|
||||
|
||||
b.HasIndex("ParentTaskId");
|
||||
|
||||
b.HasIndex("Source");
|
||||
|
||||
b.ToTable("Tasks");
|
||||
@@ -227,10 +283,25 @@ namespace Nexus.Api.Migrations
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.HasOne("Nexus.Api.Data.WorkTask", "ParentTask")
|
||||
.WithMany("ChildTasks")
|
||||
.HasForeignKey("ParentTaskId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("ParentTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.NexusUser", b =>
|
||||
{
|
||||
b.Navigation("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Nexus.Api.Data.WorkTask", b =>
|
||||
{
|
||||
b.Navigation("ChildTasks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ public sealed class NexusDbContext(DbContextOptions<NexusDbContext> options) : D
|
||||
{
|
||||
public DbSet<Project> Projects => Set<Project>();
|
||||
public DbSet<WorkTask> Tasks => Set<WorkTask>();
|
||||
public DbSet<Notification> Notifications => Set<Notification>();
|
||||
public DbSet<ActivityEvent> Activity => Set<ActivityEvent>();
|
||||
public DbSet<NexusUser> Users => Set<NexusUser>();
|
||||
public DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>();
|
||||
@@ -21,8 +22,25 @@ public sealed class NexusDbContext(DbContextOptions<NexusDbContext> options) : D
|
||||
entity.Property(x => x.AssignedTo).HasMaxLength(60);
|
||||
entity.HasIndex(x => x.Source);
|
||||
entity.HasIndex(x => x.AssignedTo);
|
||||
entity.HasOne(x => x.ParentTask)
|
||||
.WithMany(x => x.ChildTasks)
|
||||
.HasForeignKey(x => x.ParentTaskId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
modelBuilder.Entity<Notification>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Title).HasMaxLength(240);
|
||||
entity.Property(x => x.Message).HasMaxLength(1000);
|
||||
entity.Property(x => x.Type).HasMaxLength(60);
|
||||
entity.Property(x => x.ForUser).HasMaxLength(60);
|
||||
entity.HasIndex(x => new { x.ForUser, x.IsRead, x.CreatedAt });
|
||||
});
|
||||
|
||||
modelBuilder.Entity<ActivityEvent>(entity =>
|
||||
{
|
||||
entity.Property(x => x.Message).HasMaxLength(1000);
|
||||
entity.HasIndex(x => x.TaskId);
|
||||
});
|
||||
modelBuilder.Entity<ActivityEvent>().Property(x => x.Message).HasMaxLength(1000);
|
||||
modelBuilder.Entity<NexusUser>().HasIndex(u => u.NormalizedEmail).IsUnique();
|
||||
modelBuilder.Entity<RefreshToken>().HasIndex(r => r.TokenHash).IsUnique();
|
||||
modelBuilder.Entity<RefreshToken>().HasIndex(r => new { r.UserId, r.FamilyId });
|
||||
|
||||
Reference in New Issue
Block a user