feat: add feedback backend foundation
This commit is contained in:
@@ -5,6 +5,7 @@ using Socialize.Api.Modules.Assets.Data;
|
|||||||
using Socialize.Api.Modules.Clients.Data;
|
using Socialize.Api.Modules.Clients.Data;
|
||||||
using Socialize.Api.Modules.Comments.Data;
|
using Socialize.Api.Modules.Comments.Data;
|
||||||
using Socialize.Api.Modules.ContentItems.Data;
|
using Socialize.Api.Modules.ContentItems.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
using Socialize.Api.Modules.Identity.Data;
|
using Socialize.Api.Modules.Identity.Data;
|
||||||
using Socialize.Api.Modules.Notifications.Data;
|
using Socialize.Api.Modules.Notifications.Data;
|
||||||
using Socialize.Api.Modules.Projects.Data;
|
using Socialize.Api.Modules.Projects.Data;
|
||||||
@@ -28,6 +29,8 @@ public class AppDbContext(
|
|||||||
public DbSet<ApprovalRequest> ApprovalRequests => Set<ApprovalRequest>();
|
public DbSet<ApprovalRequest> ApprovalRequests => Set<ApprovalRequest>();
|
||||||
public DbSet<ApprovalDecision> ApprovalDecisions => Set<ApprovalDecision>();
|
public DbSet<ApprovalDecision> ApprovalDecisions => Set<ApprovalDecision>();
|
||||||
public DbSet<NotificationEvent> NotificationEvents => Set<NotificationEvent>();
|
public DbSet<NotificationEvent> NotificationEvents => Set<NotificationEvent>();
|
||||||
|
public DbSet<FeedbackReport> FeedbackReports => Set<FeedbackReport>();
|
||||||
|
public DbSet<FeedbackTag> FeedbackTags => Set<FeedbackTag>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
@@ -41,5 +44,6 @@ public class AppDbContext(
|
|||||||
builder.ConfigureCommentsModule();
|
builder.ConfigureCommentsModule();
|
||||||
builder.ConfigureApprovalsModule();
|
builder.ConfigureApprovalsModule();
|
||||||
builder.ConfigureNotificationsModule();
|
builder.ConfigureNotificationsModule();
|
||||||
|
builder.ConfigureFeedbackModule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1105
backend/src/Socialize.Api/Migrations/20260430072517_AddFeedbackFoundation.Designer.cs
generated
Normal file
1105
backend/src/Socialize.Api/Migrations/20260430072517_AddFeedbackFoundation.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,116 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Socialize.Api.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddFeedbackFoundation : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FeedbackReports",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Type = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||||
|
Status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||||
|
Description = table.Column<string>(type: "character varying(8000)", maxLength: 8000, nullable: false),
|
||||||
|
ReporterUserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ReporterDisplayName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
ReporterEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
SubmittedPath = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||||
|
BrowserUserAgent = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||||
|
ViewportWidth = table.Column<int>(type: "integer", nullable: true),
|
||||||
|
ViewportHeight = table.Column<int>(type: "integer", nullable: true),
|
||||||
|
AppVersion = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||||
|
WorkspaceId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
WorkspaceName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
ClientId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
ClientName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
ProjectId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
ProjectName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
ContentItemId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
ContentItemTitle = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||||
|
LastActivityAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||||
|
CancelledAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||||
|
CancelledByUserId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
CancellationReason = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FeedbackReports", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FeedbackTags",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
FeedbackReportId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||||
|
NormalizedName = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FeedbackTags", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_FeedbackTags_FeedbackReports_FeedbackReportId",
|
||||||
|
column: x => x.FeedbackReportId,
|
||||||
|
principalTable: "FeedbackReports",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FeedbackReports_LastActivityAt",
|
||||||
|
table: "FeedbackReports",
|
||||||
|
column: "LastActivityAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FeedbackReports_ReporterUserId",
|
||||||
|
table: "FeedbackReports",
|
||||||
|
column: "ReporterUserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FeedbackReports_Status",
|
||||||
|
table: "FeedbackReports",
|
||||||
|
column: "Status");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FeedbackReports_Type",
|
||||||
|
table: "FeedbackReports",
|
||||||
|
column: "Type");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FeedbackReports_WorkspaceId",
|
||||||
|
table: "FeedbackReports",
|
||||||
|
column: "WorkspaceId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FeedbackTags_FeedbackReportId_NormalizedName",
|
||||||
|
table: "FeedbackTags",
|
||||||
|
columns: new[] { "FeedbackReportId", "NormalizedName" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FeedbackTags_NormalizedName",
|
||||||
|
table: "FeedbackTags",
|
||||||
|
column: "NormalizedName");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FeedbackTags");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FeedbackReports");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -125,7 +125,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("AspNetUserTokens", (string)null);
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Approvals.Data.ApprovalDecision", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -168,7 +168,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("ApprovalDecisions", (string)null);
|
b.ToTable("ApprovalDecisions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Approvals.Data.ApprovalRequest", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -230,7 +230,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("ApprovalRequests", (string)null);
|
b.ToTable("ApprovalRequests", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Assets.Data.Asset", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -286,7 +286,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("Assets", (string)null);
|
b.ToTable("Assets", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Assets.Data.AssetRevision", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -329,7 +329,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("AssetRevisions", (string)null);
|
b.ToTable("AssetRevisions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Clients.Data.Client", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -379,7 +379,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("Clients", (string)null);
|
b.ToTable("Clients", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Comments.Data.Comment", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -434,7 +434,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("Comments", (string)null);
|
b.ToTable("Comments", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.ContentItems.Data.ContentItem", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -500,7 +500,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("ContentItems", (string)null);
|
b.ToTable("ContentItems", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.ContentItems.Data.ContentItemRevision", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -558,7 +558,150 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("ContentItemRevisions", (string)null);
|
b.ToTable("ContentItemRevisions", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Identity.Data.Role", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("AppVersion")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("BrowserUserAgent")
|
||||||
|
.HasMaxLength(1024)
|
||||||
|
.HasColumnType("character varying(1024)");
|
||||||
|
|
||||||
|
b.Property<string>("CancellationReason")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("character varying(2000)");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("CancelledAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CancelledByUserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ClientId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("ClientName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ContentItemId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("ContentItemTitle")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(8000)
|
||||||
|
.HasColumnType("character varying(8000)");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("LastActivityAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid?>("ProjectId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("ProjectName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("ReporterDisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("ReporterEmail")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<Guid>("ReporterUserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Status")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
|
b.Property<string>("SubmittedPath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
|
b.Property<int?>("ViewportHeight")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int?>("ViewportWidth")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<Guid?>("WorkspaceId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("WorkspaceName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LastActivityAt");
|
||||||
|
|
||||||
|
b.HasIndex("ReporterUserId");
|
||||||
|
|
||||||
|
b.HasIndex("Status");
|
||||||
|
|
||||||
|
b.HasIndex("Type");
|
||||||
|
|
||||||
|
b.HasIndex("WorkspaceId");
|
||||||
|
|
||||||
|
b.ToTable("FeedbackReports", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FeedbackReportId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("character varying(64)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName");
|
||||||
|
|
||||||
|
b.HasIndex("FeedbackReportId", "NormalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("FeedbackTags", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.Role", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -585,7 +728,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("AspNetRoles", (string)null);
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Identity.Data.User", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.User", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -688,7 +831,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("AspNetUsers", (string)null);
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Notifications.Data.NotificationEvent", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -750,7 +893,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("NotificationEvents", (string)null);
|
b.ToTable("NotificationEvents", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Projects.Data.Project", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Projects.Data.Project", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -803,7 +946,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("Projects", (string)null);
|
b.ToTable("Projects", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Workspaces.Data.Workspace", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -814,15 +957,15 @@ namespace Socialize.Api.Migrations
|
|||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<string>("LogoUrl")
|
||||||
|
.HasMaxLength(2048)
|
||||||
|
.HasColumnType("character varying(2048)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
b.Property<string>("LogoUrl")
|
|
||||||
.HasMaxLength(2048)
|
|
||||||
.HasColumnType("character varying(2048)");
|
|
||||||
|
|
||||||
b.Property<Guid>("OwnerUserId")
|
b.Property<Guid>("OwnerUserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
@@ -846,7 +989,7 @@ namespace Socialize.Api.Migrations
|
|||||||
b.ToTable("Workspaces", (string)null);
|
b.ToTable("Workspaces", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Socialize.Modules.Workspaces.Data.WorkspaceInvite", b =>
|
modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -889,7 +1032,7 @@ namespace Socialize.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Socialize.Modules.Identity.Data.Role", null)
|
b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("RoleId")
|
.HasForeignKey("RoleId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -898,7 +1041,7 @@ namespace Socialize.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Socialize.Modules.Identity.Data.User", null)
|
b.HasOne("Socialize.Api.Modules.Identity.Data.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -907,7 +1050,7 @@ namespace Socialize.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Socialize.Modules.Identity.Data.User", null)
|
b.HasOne("Socialize.Api.Modules.Identity.Data.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -916,13 +1059,13 @@ namespace Socialize.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Socialize.Modules.Identity.Data.Role", null)
|
b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("RoleId")
|
.HasForeignKey("RoleId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Socialize.Modules.Identity.Data.User", null)
|
b.HasOne("Socialize.Api.Modules.Identity.Data.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@@ -931,12 +1074,28 @@ namespace Socialize.Api.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Socialize.Modules.Identity.Data.User", null)
|
b.HasOne("Socialize.Api.Modules.Identity.Data.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport")
|
||||||
|
.WithMany("Tags")
|
||||||
|
.HasForeignKey("FeedbackReportId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("FeedbackReport");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Tags");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
|
||||||
|
public record FeedbackContextDto(
|
||||||
|
Guid? WorkspaceId,
|
||||||
|
string? WorkspaceName,
|
||||||
|
Guid? ClientId,
|
||||||
|
string? ClientName,
|
||||||
|
Guid? ProjectId,
|
||||||
|
string? ProjectName,
|
||||||
|
Guid? ContentItemId,
|
||||||
|
string? ContentItemTitle);
|
||||||
|
|
||||||
|
public record FeedbackMetadataDto(
|
||||||
|
string SubmittedPath,
|
||||||
|
string? BrowserUserAgent,
|
||||||
|
int? ViewportWidth,
|
||||||
|
int? ViewportHeight,
|
||||||
|
string? AppVersion);
|
||||||
|
|
||||||
|
public record FeedbackReportDto(
|
||||||
|
Guid Id,
|
||||||
|
string Type,
|
||||||
|
string Status,
|
||||||
|
string Description,
|
||||||
|
Guid ReporterUserId,
|
||||||
|
string ReporterDisplayName,
|
||||||
|
string ReporterEmail,
|
||||||
|
FeedbackMetadataDto Metadata,
|
||||||
|
FeedbackContextDto Context,
|
||||||
|
IReadOnlyCollection<string> Tags,
|
||||||
|
DateTimeOffset CreatedAt,
|
||||||
|
DateTimeOffset LastActivityAt,
|
||||||
|
DateTimeOffset? CancelledAt,
|
||||||
|
string? CancellationReason);
|
||||||
|
|
||||||
|
public static class FeedbackDtoMapper
|
||||||
|
{
|
||||||
|
public static FeedbackReportDto ToDto(this FeedbackReport report)
|
||||||
|
{
|
||||||
|
return new FeedbackReportDto(
|
||||||
|
report.Id,
|
||||||
|
ToDisplayString(report.Type),
|
||||||
|
ToDisplayString(report.Status),
|
||||||
|
report.Description,
|
||||||
|
report.ReporterUserId,
|
||||||
|
report.ReporterDisplayName,
|
||||||
|
report.ReporterEmail,
|
||||||
|
new FeedbackMetadataDto(
|
||||||
|
report.SubmittedPath,
|
||||||
|
report.BrowserUserAgent,
|
||||||
|
report.ViewportWidth,
|
||||||
|
report.ViewportHeight,
|
||||||
|
report.AppVersion),
|
||||||
|
new FeedbackContextDto(
|
||||||
|
report.WorkspaceId,
|
||||||
|
report.WorkspaceName,
|
||||||
|
report.ClientId,
|
||||||
|
report.ClientName,
|
||||||
|
report.ProjectId,
|
||||||
|
report.ProjectName,
|
||||||
|
report.ContentItemId,
|
||||||
|
report.ContentItemTitle),
|
||||||
|
report.Tags.OrderBy(tag => tag.Name).Select(tag => tag.Name).ToArray(),
|
||||||
|
report.CreatedAt,
|
||||||
|
report.LastActivityAt,
|
||||||
|
report.CancelledAt,
|
||||||
|
report.CancellationReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToDisplayString(FeedbackType type)
|
||||||
|
{
|
||||||
|
return type.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToDisplayString(FeedbackStatus status)
|
||||||
|
{
|
||||||
|
return status == FeedbackStatus.WontDo ? "Won't Do" : status.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
public static class FeedbackModelConfiguration
|
||||||
|
{
|
||||||
|
public static ModelBuilder ConfigureFeedbackModule(this ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.Entity<FeedbackReport>(feedback =>
|
||||||
|
{
|
||||||
|
feedback.ToTable("FeedbackReports");
|
||||||
|
feedback.HasKey(x => x.Id);
|
||||||
|
feedback.Property(x => x.Type).HasConversion<string>().HasMaxLength(32).IsRequired();
|
||||||
|
feedback.Property(x => x.Status).HasConversion<string>().HasMaxLength(32).IsRequired();
|
||||||
|
feedback.Property(x => x.Description).HasMaxLength(8000).IsRequired();
|
||||||
|
feedback.Property(x => x.ReporterDisplayName).HasMaxLength(256).IsRequired();
|
||||||
|
feedback.Property(x => x.ReporterEmail).HasMaxLength(256).IsRequired();
|
||||||
|
feedback.Property(x => x.SubmittedPath).HasMaxLength(2048).IsRequired();
|
||||||
|
feedback.Property(x => x.BrowserUserAgent).HasMaxLength(1024);
|
||||||
|
feedback.Property(x => x.AppVersion).HasMaxLength(128);
|
||||||
|
feedback.Property(x => x.WorkspaceName).HasMaxLength(256);
|
||||||
|
feedback.Property(x => x.ClientName).HasMaxLength(256);
|
||||||
|
feedback.Property(x => x.ProjectName).HasMaxLength(256);
|
||||||
|
feedback.Property(x => x.ContentItemTitle).HasMaxLength(256);
|
||||||
|
feedback.Property(x => x.CancellationReason).HasMaxLength(2000);
|
||||||
|
feedback.Property(x => x.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
feedback.HasIndex(x => x.ReporterUserId);
|
||||||
|
feedback.HasIndex(x => x.Status);
|
||||||
|
feedback.HasIndex(x => x.Type);
|
||||||
|
feedback.HasIndex(x => x.WorkspaceId);
|
||||||
|
feedback.HasIndex(x => x.LastActivityAt);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity<FeedbackTag>(tag =>
|
||||||
|
{
|
||||||
|
tag.ToTable("FeedbackTags");
|
||||||
|
tag.HasKey(x => x.Id);
|
||||||
|
tag.Property(x => x.Name).HasMaxLength(64).IsRequired();
|
||||||
|
tag.Property(x => x.NormalizedName).HasMaxLength(64).IsRequired();
|
||||||
|
tag.HasIndex(x => x.NormalizedName);
|
||||||
|
tag.HasIndex(x => new { x.FeedbackReportId, x.NormalizedName }).IsUnique();
|
||||||
|
tag.HasOne(x => x.FeedbackReport)
|
||||||
|
.WithMany(x => x.Tags)
|
||||||
|
.HasForeignKey(x => x.FeedbackReportId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
return modelBuilder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
namespace Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
public class FeedbackReport
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public FeedbackType Type { get; set; }
|
||||||
|
public FeedbackStatus Status { get; set; }
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public Guid ReporterUserId { get; set; }
|
||||||
|
public string ReporterDisplayName { get; set; } = string.Empty;
|
||||||
|
public string ReporterEmail { get; set; } = string.Empty;
|
||||||
|
public string SubmittedPath { get; set; } = string.Empty;
|
||||||
|
public string? BrowserUserAgent { get; set; }
|
||||||
|
public int? ViewportWidth { get; set; }
|
||||||
|
public int? ViewportHeight { get; set; }
|
||||||
|
public string? AppVersion { get; set; }
|
||||||
|
public Guid? WorkspaceId { get; set; }
|
||||||
|
public string? WorkspaceName { get; set; }
|
||||||
|
public Guid? ClientId { get; set; }
|
||||||
|
public string? ClientName { get; set; }
|
||||||
|
public Guid? ProjectId { get; set; }
|
||||||
|
public string? ProjectName { get; set; }
|
||||||
|
public Guid? ContentItemId { get; set; }
|
||||||
|
public string? ContentItemTitle { get; set; }
|
||||||
|
public DateTimeOffset CreatedAt { get; set; }
|
||||||
|
public DateTimeOffset LastActivityAt { get; set; }
|
||||||
|
public DateTimeOffset? CancelledAt { get; set; }
|
||||||
|
public Guid? CancelledByUserId { get; set; }
|
||||||
|
public string? CancellationReason { get; set; }
|
||||||
|
public ICollection<FeedbackTag> Tags { get; } = new List<FeedbackTag>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
public enum FeedbackStatus
|
||||||
|
{
|
||||||
|
New,
|
||||||
|
Planned,
|
||||||
|
Resolved,
|
||||||
|
WontDo,
|
||||||
|
Cancelled,
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
public class FeedbackTag
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid FeedbackReportId { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string NormalizedName { get; set; } = string.Empty;
|
||||||
|
public FeedbackReport? FeedbackReport { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
public enum FeedbackType
|
||||||
|
{
|
||||||
|
Bug,
|
||||||
|
Suggestion,
|
||||||
|
Request,
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Socialize.Api.Modules.Feedback;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
public static WebApplicationBuilder AddFeedbackModule(this WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Infrastructure.Security;
|
||||||
|
using Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Services;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public record CancelMyFeedbackRequest(string? Reason);
|
||||||
|
|
||||||
|
public class CancelMyFeedbackRequestValidator
|
||||||
|
: Validator<CancelMyFeedbackRequest>
|
||||||
|
{
|
||||||
|
public CancelMyFeedbackRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Reason).MaximumLength(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CancelMyFeedbackHandler(AppDbContext dbContext)
|
||||||
|
: Endpoint<CancelMyFeedbackRequest, FeedbackReportDto>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Post("/api/my-feedback/{id}/cancel");
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CancelMyFeedbackRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
Guid id = Route<Guid>("id");
|
||||||
|
Guid reporterUserId = User.GetUserId();
|
||||||
|
|
||||||
|
FeedbackReport? report = await dbContext.FeedbackReports
|
||||||
|
.Include(candidate => candidate.Tags)
|
||||||
|
.SingleOrDefaultAsync(
|
||||||
|
candidate => candidate.Id == id && candidate.ReporterUserId == reporterUserId,
|
||||||
|
ct);
|
||||||
|
|
||||||
|
if (report is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FeedbackAccessRules.CanReporterCancel(report, reporterUserId))
|
||||||
|
{
|
||||||
|
AddError("The feedback report cannot be cancelled.");
|
||||||
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
|
report.Status = FeedbackStatus.Cancelled;
|
||||||
|
report.CancelledAt = now;
|
||||||
|
report.CancelledByUserId = reporterUserId;
|
||||||
|
report.CancellationReason = string.IsNullOrWhiteSpace(request.Reason) ? null : request.Reason.Trim();
|
||||||
|
report.LastActivityAt = now;
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(ct);
|
||||||
|
await SendOkAsync(report.ToDto(), ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
using Socialize.Api.Modules.Identity.Contracts;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public class GetDeveloperFeedbackHandler(AppDbContext dbContext)
|
||||||
|
: EndpointWithoutRequest<FeedbackReportDto>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/feedback/{id}");
|
||||||
|
Roles(KnownRoles.Developer);
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
Guid id = Route<Guid>("id");
|
||||||
|
FeedbackReportDto? report = await dbContext.FeedbackReports
|
||||||
|
.Include(candidate => candidate.Tags)
|
||||||
|
.Where(candidate => candidate.Id == id)
|
||||||
|
.Select(candidate => candidate.ToDto())
|
||||||
|
.SingleOrDefaultAsync(ct);
|
||||||
|
|
||||||
|
if (report is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(report, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Infrastructure.Security;
|
||||||
|
using Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public class GetMyFeedbackHandler(AppDbContext dbContext)
|
||||||
|
: EndpointWithoutRequest<FeedbackReportDto>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/my-feedback/{id}");
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
Guid id = Route<Guid>("id");
|
||||||
|
Guid reporterUserId = User.GetUserId();
|
||||||
|
|
||||||
|
FeedbackReportDto? report = await dbContext.FeedbackReports
|
||||||
|
.Include(candidate => candidate.Tags)
|
||||||
|
.Where(candidate => candidate.Id == id && candidate.ReporterUserId == reporterUserId)
|
||||||
|
.Select(candidate => candidate.ToDto())
|
||||||
|
.SingleOrDefaultAsync(ct);
|
||||||
|
|
||||||
|
if (report is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(report, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
using Socialize.Api.Modules.Identity.Contracts;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public class ListDeveloperFeedbackHandler(AppDbContext dbContext)
|
||||||
|
: EndpointWithoutRequest<IReadOnlyCollection<FeedbackReportDto>>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/feedback");
|
||||||
|
Roles(KnownRoles.Developer);
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
List<FeedbackReportDto> reports = await dbContext.FeedbackReports
|
||||||
|
.Include(report => report.Tags)
|
||||||
|
.OrderByDescending(report => report.LastActivityAt)
|
||||||
|
.Select(report => report.ToDto())
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
await SendOkAsync(reports, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Modules.Identity.Contracts;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public class ListFeedbackTagsHandler(AppDbContext dbContext)
|
||||||
|
: EndpointWithoutRequest<IReadOnlyCollection<string>>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/feedback/tags");
|
||||||
|
Roles(KnownRoles.Developer);
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
List<string> tags = await dbContext.FeedbackTags
|
||||||
|
.GroupBy(tag => new { tag.NormalizedName, tag.Name })
|
||||||
|
.OrderBy(group => group.Key.Name)
|
||||||
|
.Select(group => group.Key.Name)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
await SendOkAsync(tags, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Infrastructure.Security;
|
||||||
|
using Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public class ListMyFeedbackHandler(AppDbContext dbContext)
|
||||||
|
: EndpointWithoutRequest<IReadOnlyCollection<FeedbackReportDto>>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Get("/api/my-feedback");
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
Guid reporterUserId = User.GetUserId();
|
||||||
|
List<FeedbackReportDto> reports = await dbContext.FeedbackReports
|
||||||
|
.Include(report => report.Tags)
|
||||||
|
.Where(report => report.ReporterUserId == reporterUserId)
|
||||||
|
.OrderByDescending(report => report.LastActivityAt)
|
||||||
|
.Select(report => report.ToDto())
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
await SendOkAsync(reports, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Infrastructure.Security;
|
||||||
|
using Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Services;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public record SubmitFeedbackRequest(
|
||||||
|
string Type,
|
||||||
|
string Description,
|
||||||
|
string SubmittedPath,
|
||||||
|
string? BrowserUserAgent,
|
||||||
|
int? ViewportWidth,
|
||||||
|
int? ViewportHeight,
|
||||||
|
string? AppVersion,
|
||||||
|
Guid? WorkspaceId,
|
||||||
|
string? WorkspaceName,
|
||||||
|
Guid? ClientId,
|
||||||
|
string? ClientName,
|
||||||
|
Guid? ProjectId,
|
||||||
|
string? ProjectName,
|
||||||
|
Guid? ContentItemId,
|
||||||
|
string? ContentItemTitle);
|
||||||
|
|
||||||
|
public class SubmitFeedbackRequestValidator
|
||||||
|
: Validator<SubmitFeedbackRequest>
|
||||||
|
{
|
||||||
|
public SubmitFeedbackRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Type).NotEmpty().MaximumLength(32);
|
||||||
|
RuleFor(x => x.Description).NotEmpty().MaximumLength(8000);
|
||||||
|
RuleFor(x => x.SubmittedPath).NotEmpty().MaximumLength(2048);
|
||||||
|
RuleFor(x => x.BrowserUserAgent).MaximumLength(1024);
|
||||||
|
RuleFor(x => x.AppVersion).MaximumLength(128);
|
||||||
|
RuleFor(x => x.WorkspaceName).MaximumLength(256);
|
||||||
|
RuleFor(x => x.ClientName).MaximumLength(256);
|
||||||
|
RuleFor(x => x.ProjectName).MaximumLength(256);
|
||||||
|
RuleFor(x => x.ContentItemTitle).MaximumLength(256);
|
||||||
|
RuleFor(x => x.ViewportWidth).GreaterThan(0).When(x => x.ViewportWidth.HasValue);
|
||||||
|
RuleFor(x => x.ViewportHeight).GreaterThan(0).When(x => x.ViewportHeight.HasValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubmitFeedbackHandler(AppDbContext dbContext)
|
||||||
|
: Endpoint<SubmitFeedbackRequest, FeedbackReportDto>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Post("/api/feedback");
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(SubmitFeedbackRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (!FeedbackRules.TryParseType(request.Type, out FeedbackType type))
|
||||||
|
{
|
||||||
|
AddError(request => request.Type, "The selected feedback type is not valid.");
|
||||||
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
|
FeedbackReport report = new()
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Type = type,
|
||||||
|
Status = FeedbackStatus.New,
|
||||||
|
Description = request.Description.Trim(),
|
||||||
|
ReporterUserId = User.GetUserId(),
|
||||||
|
ReporterDisplayName = User.GetAlias() ?? User.GetName(),
|
||||||
|
ReporterEmail = User.GetEmail(),
|
||||||
|
SubmittedPath = request.SubmittedPath.Trim(),
|
||||||
|
BrowserUserAgent = NormalizeOptional(request.BrowserUserAgent),
|
||||||
|
ViewportWidth = request.ViewportWidth,
|
||||||
|
ViewportHeight = request.ViewportHeight,
|
||||||
|
AppVersion = NormalizeOptional(request.AppVersion),
|
||||||
|
WorkspaceId = request.WorkspaceId,
|
||||||
|
WorkspaceName = NormalizeOptional(request.WorkspaceName),
|
||||||
|
ClientId = request.ClientId,
|
||||||
|
ClientName = NormalizeOptional(request.ClientName),
|
||||||
|
ProjectId = request.ProjectId,
|
||||||
|
ProjectName = NormalizeOptional(request.ProjectName),
|
||||||
|
ContentItemId = request.ContentItemId,
|
||||||
|
ContentItemTitle = NormalizeOptional(request.ContentItemTitle),
|
||||||
|
CreatedAt = now,
|
||||||
|
LastActivityAt = now,
|
||||||
|
};
|
||||||
|
|
||||||
|
dbContext.FeedbackReports.Add(report);
|
||||||
|
await dbContext.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
await SendAsync(report.ToDto(), StatusCodes.Status201Created, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? NormalizeOptional(string? value)
|
||||||
|
{
|
||||||
|
string? normalized = value?.Trim();
|
||||||
|
return string.IsNullOrWhiteSpace(normalized) ? null : normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
using FastEndpoints;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Socialize.Api.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Contracts;
|
||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Services;
|
||||||
|
using Socialize.Api.Modules.Identity.Contracts;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Handlers;
|
||||||
|
|
||||||
|
public record UpdateDeveloperFeedbackRequest(
|
||||||
|
string? Type,
|
||||||
|
string? Status,
|
||||||
|
IReadOnlyCollection<string>? Tags);
|
||||||
|
|
||||||
|
public class UpdateDeveloperFeedbackRequestValidator
|
||||||
|
: Validator<UpdateDeveloperFeedbackRequest>
|
||||||
|
{
|
||||||
|
public UpdateDeveloperFeedbackRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Type).MaximumLength(32);
|
||||||
|
RuleFor(x => x.Status).MaximumLength(32);
|
||||||
|
RuleForEach(x => x.Tags).MaximumLength(64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateDeveloperFeedbackHandler(AppDbContext dbContext)
|
||||||
|
: Endpoint<UpdateDeveloperFeedbackRequest, FeedbackReportDto>
|
||||||
|
{
|
||||||
|
public override void Configure()
|
||||||
|
{
|
||||||
|
Patch("/api/feedback/{id}");
|
||||||
|
Roles(KnownRoles.Developer);
|
||||||
|
Options(o => o.WithTags("Feedback"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task HandleAsync(UpdateDeveloperFeedbackRequest request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
Guid id = Route<Guid>("id");
|
||||||
|
FeedbackReport? report = await dbContext.FeedbackReports
|
||||||
|
.Include(candidate => candidate.Tags)
|
||||||
|
.SingleOrDefaultAsync(candidate => candidate.Id == id, ct);
|
||||||
|
|
||||||
|
if (report is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Type))
|
||||||
|
{
|
||||||
|
if (!FeedbackRules.TryParseType(request.Type, out FeedbackType nextType))
|
||||||
|
{
|
||||||
|
AddError(request => request.Type, "The selected feedback type is not valid.");
|
||||||
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.Type != nextType)
|
||||||
|
{
|
||||||
|
report.Type = nextType;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Status))
|
||||||
|
{
|
||||||
|
if (!FeedbackRules.TryParseStatus(request.Status, out FeedbackStatus nextStatus))
|
||||||
|
{
|
||||||
|
AddError(request => request.Status, "The selected feedback status is not valid.");
|
||||||
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FeedbackRules.CanDeveloperSetStatus(report.Status, nextStatus))
|
||||||
|
{
|
||||||
|
AddError(request => request.Status, "The requested status transition is not allowed.");
|
||||||
|
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report.Status != nextStatus)
|
||||||
|
{
|
||||||
|
report.Status = nextStatus;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Tags is not null)
|
||||||
|
{
|
||||||
|
IReadOnlyCollection<string> normalizedTags = FeedbackRules.NormalizeTags(request.Tags);
|
||||||
|
ApplyTags(report, normalizedTags);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
report.LastActivityAt = DateTimeOffset.UtcNow;
|
||||||
|
await dbContext.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(report.ToDto(), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyTags(FeedbackReport report, IReadOnlyCollection<string> tags)
|
||||||
|
{
|
||||||
|
HashSet<string> requestedKeys = tags
|
||||||
|
.Select(FeedbackRules.NormalizeTagKey)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
foreach (FeedbackTag existingTag in report.Tags.ToArray())
|
||||||
|
{
|
||||||
|
if (!requestedKeys.Contains(existingTag.NormalizedName))
|
||||||
|
{
|
||||||
|
report.Tags.Remove(existingTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<string> existingKeys = report.Tags
|
||||||
|
.Select(tag => tag.NormalizedName)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
foreach (string tag in tags)
|
||||||
|
{
|
||||||
|
string key = FeedbackRules.NormalizeTagKey(tag);
|
||||||
|
if (existingKeys.Contains(key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
report.Tags.Add(new FeedbackTag
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
FeedbackReportId = report.Id,
|
||||||
|
Name = tag,
|
||||||
|
NormalizedName = key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Services;
|
||||||
|
|
||||||
|
public static class FeedbackAccessRules
|
||||||
|
{
|
||||||
|
public static bool CanReporterAccess(FeedbackReport report, Guid userId)
|
||||||
|
{
|
||||||
|
return report.ReporterUserId == userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CanReporterCancel(FeedbackReport report, Guid userId)
|
||||||
|
{
|
||||||
|
return CanReporterAccess(report, userId) && FeedbackRules.CanReporterCancel(report.Status);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
|
|
||||||
|
namespace Socialize.Api.Modules.Feedback.Services;
|
||||||
|
|
||||||
|
public static class FeedbackRules
|
||||||
|
{
|
||||||
|
public static bool TryParseType(string? value, out FeedbackType type)
|
||||||
|
{
|
||||||
|
return Enum.TryParse(value?.Trim(), ignoreCase: true, out type)
|
||||||
|
&& Enum.IsDefined(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParseStatus(string? value, out FeedbackStatus status)
|
||||||
|
{
|
||||||
|
string? normalized = value?.Trim().Replace("'", string.Empty, StringComparison.Ordinal);
|
||||||
|
if (string.Equals(normalized, "Wont Do", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(normalized, "WontDo", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
status = FeedbackStatus.WontDo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enum.TryParse(normalized, ignoreCase: true, out status)
|
||||||
|
&& Enum.IsDefined(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsFinal(FeedbackStatus status)
|
||||||
|
{
|
||||||
|
return status is FeedbackStatus.Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CanDeveloperSetStatus(FeedbackStatus currentStatus, FeedbackStatus nextStatus)
|
||||||
|
{
|
||||||
|
return !IsFinal(currentStatus) &&
|
||||||
|
nextStatus is FeedbackStatus.New or FeedbackStatus.Planned or FeedbackStatus.Resolved or FeedbackStatus.WontDo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CanReporterCancel(FeedbackStatus currentStatus)
|
||||||
|
{
|
||||||
|
return !IsFinal(currentStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyCollection<string> NormalizeTags(IEnumerable<string>? tags)
|
||||||
|
{
|
||||||
|
if (tags is null)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags
|
||||||
|
.Select(tag => tag.Trim())
|
||||||
|
.Where(tag => !string.IsNullOrWhiteSpace(tag))
|
||||||
|
.Select(tag => tag.Length > 64 ? tag[..64] : tag)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Order(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string NormalizeTagKey(string tag)
|
||||||
|
{
|
||||||
|
return tag.Trim().ToUpperInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,4 +7,5 @@ public static class KnownRoles
|
|||||||
public const string Client = nameof(Client);
|
public const string Client = nameof(Client);
|
||||||
public const string Provider = nameof(Provider);
|
public const string Provider = nameof(Provider);
|
||||||
public const string WorkspaceMember = nameof(WorkspaceMember);
|
public const string WorkspaceMember = nameof(WorkspaceMember);
|
||||||
|
public const string Developer = nameof(Developer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,5 +97,11 @@ public static class DependencyInjection
|
|||||||
{
|
{
|
||||||
await roleManager.CreateAsync(workspaceMemberRole);
|
await roleManager.CreateAsync(workspaceMemberRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Role developerRole = new(KnownRoles.Developer);
|
||||||
|
if (roleManager.Roles.All(r => r.Name != developerRole.Name))
|
||||||
|
{
|
||||||
|
await roleManager.CreateAsync(developerRole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Socialize.Api.Modules.Assets;
|
|||||||
using Socialize.Api.Modules.Clients;
|
using Socialize.Api.Modules.Clients;
|
||||||
using Socialize.Api.Modules.Comments;
|
using Socialize.Api.Modules.Comments;
|
||||||
using Socialize.Api.Modules.ContentItems;
|
using Socialize.Api.Modules.ContentItems;
|
||||||
|
using Socialize.Api.Modules.Feedback;
|
||||||
using Socialize.Api.Modules.Identity;
|
using Socialize.Api.Modules.Identity;
|
||||||
using Socialize.Api.Modules.Notifications;
|
using Socialize.Api.Modules.Notifications;
|
||||||
using Socialize.Api.Modules.Projects;
|
using Socialize.Api.Modules.Projects;
|
||||||
@@ -69,6 +70,7 @@ builder.AddAssetsModule();
|
|||||||
builder.AddCommentsModule();
|
builder.AddCommentsModule();
|
||||||
builder.AddApprovalsModule();
|
builder.AddApprovalsModule();
|
||||||
builder.AddNotificationsModule();
|
builder.AddNotificationsModule();
|
||||||
|
builder.AddFeedbackModule();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
115
backend/tests/Socialize.Tests/Feedback/FeedbackRulesTests.cs
Normal file
115
backend/tests/Socialize.Tests/Feedback/FeedbackRulesTests.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using Socialize.Api.Modules.Feedback.Data;
|
||||||
|
using Socialize.Api.Modules.Feedback.Services;
|
||||||
|
|
||||||
|
namespace Socialize.Tests.Feedback;
|
||||||
|
|
||||||
|
public class FeedbackRulesTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Bug", FeedbackType.Bug)]
|
||||||
|
[InlineData("suggestion", FeedbackType.Suggestion)]
|
||||||
|
[InlineData("Request", FeedbackType.Request)]
|
||||||
|
public void TryParseType_accepts_supported_types(string value, FeedbackType expected)
|
||||||
|
{
|
||||||
|
bool parsed = FeedbackRules.TryParseType(value, out FeedbackType type);
|
||||||
|
|
||||||
|
Assert.True(parsed);
|
||||||
|
Assert.Equal(expected, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("")]
|
||||||
|
[InlineData("Question")]
|
||||||
|
[InlineData("Incident")]
|
||||||
|
public void TryParseType_rejects_unsupported_types(string value)
|
||||||
|
{
|
||||||
|
bool parsed = FeedbackRules.TryParseType(value, out _);
|
||||||
|
|
||||||
|
Assert.False(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("New", FeedbackStatus.New)]
|
||||||
|
[InlineData("Planned", FeedbackStatus.Planned)]
|
||||||
|
[InlineData("Resolved", FeedbackStatus.Resolved)]
|
||||||
|
[InlineData("Won't Do", FeedbackStatus.WontDo)]
|
||||||
|
[InlineData("WontDo", FeedbackStatus.WontDo)]
|
||||||
|
[InlineData("Cancelled", FeedbackStatus.Cancelled)]
|
||||||
|
public void TryParseStatus_accepts_supported_statuses(string value, FeedbackStatus expected)
|
||||||
|
{
|
||||||
|
bool parsed = FeedbackRules.TryParseStatus(value, out FeedbackStatus status);
|
||||||
|
|
||||||
|
Assert.True(parsed);
|
||||||
|
Assert.Equal(expected, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanDeveloperSetStatus_rejects_cancelled_destination()
|
||||||
|
{
|
||||||
|
bool allowed = FeedbackRules.CanDeveloperSetStatus(FeedbackStatus.New, FeedbackStatus.Cancelled);
|
||||||
|
|
||||||
|
Assert.False(allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanDeveloperSetStatus_rejects_changes_after_cancelled()
|
||||||
|
{
|
||||||
|
bool allowed = FeedbackRules.CanDeveloperSetStatus(FeedbackStatus.Cancelled, FeedbackStatus.Planned);
|
||||||
|
|
||||||
|
Assert.False(allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanReporterCancel_rejects_cancelled_report()
|
||||||
|
{
|
||||||
|
bool allowed = FeedbackRules.CanReporterCancel(FeedbackStatus.Cancelled);
|
||||||
|
|
||||||
|
Assert.False(allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanReporterAccess_allows_report_owner()
|
||||||
|
{
|
||||||
|
Guid reporterUserId = Guid.NewGuid();
|
||||||
|
FeedbackReport report = new() { ReporterUserId = reporterUserId };
|
||||||
|
|
||||||
|
bool allowed = FeedbackAccessRules.CanReporterAccess(report, reporterUserId);
|
||||||
|
|
||||||
|
Assert.True(allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanReporterAccess_rejects_other_users()
|
||||||
|
{
|
||||||
|
FeedbackReport report = new() { ReporterUserId = Guid.NewGuid() };
|
||||||
|
|
||||||
|
bool allowed = FeedbackAccessRules.CanReporterAccess(report, Guid.NewGuid());
|
||||||
|
|
||||||
|
Assert.False(allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanReporterCancel_requires_owner_and_non_final_status()
|
||||||
|
{
|
||||||
|
Guid reporterUserId = Guid.NewGuid();
|
||||||
|
FeedbackReport report = new()
|
||||||
|
{
|
||||||
|
ReporterUserId = reporterUserId,
|
||||||
|
Status = FeedbackStatus.New,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ownerAllowed = FeedbackAccessRules.CanReporterCancel(report, reporterUserId);
|
||||||
|
bool otherUserAllowed = FeedbackAccessRules.CanReporterCancel(report, Guid.NewGuid());
|
||||||
|
|
||||||
|
Assert.True(ownerAllowed);
|
||||||
|
Assert.False(otherUserAllowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NormalizeTags_trims_deduplicates_and_orders()
|
||||||
|
{
|
||||||
|
IReadOnlyCollection<string> tags = FeedbackRules.NormalizeTags([" mobile ", "bug", "Mobile", ""]);
|
||||||
|
|
||||||
|
Assert.Equal(["bug", "mobile"], tags);
|
||||||
|
}
|
||||||
|
}
|
||||||
497
frontend/src/api/schema.d.ts
vendored
497
frontend/src/api/schema.d.ts
vendored
@@ -4,6 +4,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export interface paths {
|
export interface paths {
|
||||||
|
"/api/storage": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["GetApiStorage"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/workspaces/{id}/logo": {
|
"/api/workspaces/{id}/logo": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -420,6 +436,102 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/my-feedback/{id}/cancel": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post: operations["SocializeApiModulesFeedbackHandlersCancelMyFeedbackHandler"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/feedback/{id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["SocializeApiModulesFeedbackHandlersGetDeveloperFeedbackHandler"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch: operations["SocializeApiModulesFeedbackHandlersUpdateDeveloperFeedbackHandler"];
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/my-feedback/{id}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["SocializeApiModulesFeedbackHandlersGetMyFeedbackHandler"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/feedback": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["SocializeApiModulesFeedbackHandlersListDeveloperFeedbackHandler"];
|
||||||
|
put?: never;
|
||||||
|
post: operations["SocializeApiModulesFeedbackHandlersSubmitFeedbackHandler"];
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/feedback/tags": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["SocializeApiModulesFeedbackHandlersListFeedbackTagsHandler"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/api/my-feedback": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: operations["SocializeApiModulesFeedbackHandlersListMyFeedbackHandler"];
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/content-items": {
|
"/api/content-items": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -874,6 +986,81 @@ export interface components {
|
|||||||
message?: string;
|
message?: string;
|
||||||
};
|
};
|
||||||
SocializeApiModulesIdentityHandlersVerifyEmailRequest: Record<string, never>;
|
SocializeApiModulesIdentityHandlersVerifyEmailRequest: Record<string, never>;
|
||||||
|
SocializeApiModulesFeedbackContractsFeedbackReportDto: {
|
||||||
|
/** Format: guid */
|
||||||
|
id?: string;
|
||||||
|
type?: string;
|
||||||
|
status?: string;
|
||||||
|
description?: string;
|
||||||
|
/** Format: guid */
|
||||||
|
reporterUserId?: string;
|
||||||
|
reporterDisplayName?: string;
|
||||||
|
reporterEmail?: string;
|
||||||
|
metadata?: components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackMetadataDto"];
|
||||||
|
context?: components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackContextDto"];
|
||||||
|
tags?: string[];
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
lastActivityAt?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
cancelledAt?: string | null;
|
||||||
|
cancellationReason?: string | null;
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackContractsFeedbackMetadataDto: {
|
||||||
|
submittedPath?: string;
|
||||||
|
browserUserAgent?: string | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
viewportWidth?: number | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
viewportHeight?: number | null;
|
||||||
|
appVersion?: string | null;
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackContractsFeedbackContextDto: {
|
||||||
|
/** Format: guid */
|
||||||
|
workspaceId?: string | null;
|
||||||
|
workspaceName?: string | null;
|
||||||
|
/** Format: guid */
|
||||||
|
clientId?: string | null;
|
||||||
|
clientName?: string | null;
|
||||||
|
/** Format: guid */
|
||||||
|
projectId?: string | null;
|
||||||
|
projectName?: string | null;
|
||||||
|
/** Format: guid */
|
||||||
|
contentItemId?: string | null;
|
||||||
|
contentItemTitle?: string | null;
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersCancelMyFeedbackRequest: {
|
||||||
|
reason?: string | null;
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersSubmitFeedbackRequest: {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
submittedPath: string;
|
||||||
|
browserUserAgent?: string | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
viewportWidth?: number | null;
|
||||||
|
/** Format: int32 */
|
||||||
|
viewportHeight?: number | null;
|
||||||
|
appVersion?: string | null;
|
||||||
|
/** Format: guid */
|
||||||
|
workspaceId?: string | null;
|
||||||
|
workspaceName?: string | null;
|
||||||
|
/** Format: guid */
|
||||||
|
clientId?: string | null;
|
||||||
|
clientName?: string | null;
|
||||||
|
/** Format: guid */
|
||||||
|
projectId?: string | null;
|
||||||
|
projectName?: string | null;
|
||||||
|
/** Format: guid */
|
||||||
|
contentItemId?: string | null;
|
||||||
|
contentItemTitle?: string | null;
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersUpdateDeveloperFeedbackRequest: {
|
||||||
|
type?: string | null;
|
||||||
|
status?: string | null;
|
||||||
|
tags?: string[] | null;
|
||||||
|
};
|
||||||
SocializeApiModulesContentItemsHandlersContentItemDto: {
|
SocializeApiModulesContentItemsHandlersContentItemDto: {
|
||||||
/** Format: guid */
|
/** Format: guid */
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -1146,6 +1333,25 @@ export interface components {
|
|||||||
}
|
}
|
||||||
export type $defs = Record<string, never>;
|
export type $defs = Record<string, never>;
|
||||||
export interface operations {
|
export interface operations {
|
||||||
|
GetApiStorage: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
blobPath: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoHandler: {
|
SocializeApiModulesWorkspacesHandlersChangeWorkspaceLogoHandler: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -2032,6 +2238,297 @@ export interface operations {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersCancelMyFeedbackHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackHandlersCancelMyFeedbackRequest"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackReportDto"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Bad Request */
|
||||||
|
400: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/problem+json": components["schemas"]["FastEndpointsErrorResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersGetDeveloperFeedbackHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackReportDto"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
/** @description Forbidden */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersUpdateDeveloperFeedbackHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackHandlersUpdateDeveloperFeedbackRequest"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackReportDto"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Bad Request */
|
||||||
|
400: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/problem+json": components["schemas"]["FastEndpointsErrorResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
/** @description Forbidden */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersGetMyFeedbackHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackReportDto"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersListDeveloperFeedbackHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackReportDto"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
/** @description Forbidden */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersSubmitFeedbackHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackHandlersSubmitFeedbackRequest"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackReportDto"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Bad Request */
|
||||||
|
400: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/problem+json": components["schemas"]["FastEndpointsErrorResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersListFeedbackTagsHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
/** @description Forbidden */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
SocializeApiModulesFeedbackHandlersListMyFeedbackHandler: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Success */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["SocializeApiModulesFeedbackContractsFeedbackReportDto"][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Unauthorized */
|
||||||
|
401: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content?: never;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
SocializeApiModulesContentItemsHandlersGetContentItemsHandler: {
|
SocializeApiModulesContentItemsHandlersGetContentItemsHandler: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
query?: {
|
||||||
|
|||||||
@@ -7,10 +7,31 @@
|
|||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"url": "http://127.0.0.1:5081"
|
"url": "http://localhost:5080"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/api/storage": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "GetApiStorage",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "blobPath",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-position": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/workspaces/{id}/logo": {
|
"/api/workspaces/{id}/logo": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -1200,6 +1221,372 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/my-feedback/{id}/cancel": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersCancelMyFeedbackHandler",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"x-name": "CancelMyFeedbackRequest",
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackHandlersCancelMyFeedbackRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"x-position": 1
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackReportDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/problem+json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/FastEndpointsErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/feedback/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersGetDeveloperFeedbackHandler",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackReportDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": [
|
||||||
|
"Developer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersUpdateDeveloperFeedbackHandler",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"x-name": "UpdateDeveloperFeedbackRequest",
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackHandlersUpdateDeveloperFeedbackRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"x-position": 1
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackReportDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/problem+json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/FastEndpointsErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": [
|
||||||
|
"Developer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/my-feedback/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersGetMyFeedbackHandler",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackReportDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/feedback": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersListDeveloperFeedbackHandler",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackReportDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": [
|
||||||
|
"Developer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersSubmitFeedbackHandler",
|
||||||
|
"requestBody": {
|
||||||
|
"x-name": "SubmitFeedbackRequest",
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackHandlersSubmitFeedbackRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"x-position": 1
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackReportDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"content": {
|
||||||
|
"application/problem+json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/FastEndpointsErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/feedback/tags": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersListFeedbackTagsHandler",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": [
|
||||||
|
"Developer"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/my-feedback": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Feedback",
|
||||||
|
"Api"
|
||||||
|
],
|
||||||
|
"operationId": "SocializeApiModulesFeedbackHandlersListMyFeedbackHandler",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackReportDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTBearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/content-items": {
|
"/api/content-items": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -2900,6 +3287,271 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"SocializeApiModulesFeedbackContractsFeedbackReportDto": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reporterUserId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid"
|
||||||
|
},
|
||||||
|
"reporterDisplayName": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reporterEmail": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackMetadataDto"
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"$ref": "#/components/schemas/SocializeApiModulesFeedbackContractsFeedbackContextDto"
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"lastActivityAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"cancelledAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"cancellationReason": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SocializeApiModulesFeedbackContractsFeedbackMetadataDto": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"submittedPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"browserUserAgent": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"viewportWidth": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"viewportHeight": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"appVersion": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SocializeApiModulesFeedbackContractsFeedbackContextDto": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"workspaceId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"workspaceName": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"clientId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"clientName": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"projectId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"projectName": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"contentItemId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"contentItemTitle": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SocializeApiModulesFeedbackHandlersCancelMyFeedbackRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"reason": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 2000,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SocializeApiModulesFeedbackHandlersSubmitFeedbackRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"description",
|
||||||
|
"submittedPath"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 8000,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"submittedPath": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 2048,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": false
|
||||||
|
},
|
||||||
|
"browserUserAgent": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1024,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"viewportWidth": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"minimum": 0.0,
|
||||||
|
"nullable": true,
|
||||||
|
"exclusiveMinimum": true
|
||||||
|
},
|
||||||
|
"viewportHeight": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"minimum": 0.0,
|
||||||
|
"nullable": true,
|
||||||
|
"exclusiveMinimum": true
|
||||||
|
},
|
||||||
|
"appVersion": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 128,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"workspaceId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"workspaceName": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 256,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"clientId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"clientName": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 256,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"projectId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"projectName": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 256,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"contentItemId": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "guid",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"contentItemTitle": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 256,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"SocializeApiModulesFeedbackHandlersUpdateDeveloperFeedbackRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 32,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"maxLength": 64,
|
||||||
|
"minLength": 0,
|
||||||
|
"nullable": true,
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"SocializeApiModulesContentItemsHandlersContentItemDto": {
|
"SocializeApiModulesContentItemsHandlersContentItemDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user