From bc1ce0bbaa98b75a1564d3ea142efabfd8d96230 Mon Sep 17 00:00:00 2001 From: Jonathan Bourdon Date: Tue, 27 Jan 2026 14:34:05 -0500 Subject: [PATCH] feat(links): add models --- .idea/dataSources.xml | 12 + ...dShortLinksQRCodesEventsAssets.Designer.cs | 540 ++++++++++++++++++ ...193159_AddShortLinksQRCodesEventsAssets.cs | 239 ++++++++ src/api/Models/Asset.cs | 20 + src/api/Models/Event.cs | 28 + src/api/Models/QRCodeDesign.cs | 20 + src/api/Models/ShortLink.cs | 30 + 7 files changed, 889 insertions(+) create mode 100644 .idea/dataSources.xml create mode 100644 src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.Designer.cs create mode 100644 src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.cs create mode 100644 src/api/Models/Asset.cs create mode 100644 src/api/Models/Event.cs create mode 100644 src/api/Models/QRCodeDesign.cs create mode 100644 src/api/Models/ShortLink.cs diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..258943d --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5400/trakqr + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.Designer.cs b/src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.Designer.cs new file mode 100644 index 0000000..465e112 --- /dev/null +++ b/src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.Designer.cs @@ -0,0 +1,540 @@ +// +using System; +using Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260127193159_AddShortLinksQRCodesEventsAssets")] + partial class AddShortLinksQRCodesEventsAssets + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Api.Models.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Mime") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("StorageKey") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Assets"); + }); + + modelBuilder.Entity("Api.Models.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Hostname") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VerificationToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Hostname") + .IsUnique(); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Api.Models.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CountryCode") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("DedupeKey") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DeviceType") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("IpHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("QRCodeId") + .HasColumnType("uuid"); + + b.Property("Referrer") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ShortLinkId") + .HasColumnType("uuid"); + + b.Property("Timestamp") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("UserAgent") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("QRCodeId"); + + b.HasIndex("Timestamp"); + + b.HasIndex("ShortLinkId", "Timestamp"); + + b.HasIndex("WorkspaceId", "Timestamp"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("Api.Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("Api.Models.QRCodeDesign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LogoAssetId") + .HasColumnType("uuid"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("ShortLinkId") + .HasColumnType("uuid"); + + b.Property("StyleJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LogoAssetId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("ShortLinkId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("QRCodeDesigns"); + }); + + modelBuilder.Entity("Api.Models.ShortLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DestinationUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("DomainId") + .HasColumnType("uuid"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PasswordHash") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProjectId") + .HasColumnType("uuid"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Title") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("DomainId", "Slug") + .IsUnique(); + + b.ToTable("ShortLinks"); + }); + + modelBuilder.Entity("Api.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("PasswordHash") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("VerifiedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Api.Models.Workspace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.Property("Plan") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Workspaces"); + }); + + modelBuilder.Entity("Api.Models.Asset", b => + { + b.HasOne("Api.Models.Workspace", "Workspace") + .WithMany("Assets") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Api.Models.Domain", b => + { + b.HasOne("Api.Models.Workspace", "Workspace") + .WithMany("Domains") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Api.Models.Event", b => + { + b.HasOne("Api.Models.QRCodeDesign", "QRCode") + .WithMany("Events") + .HasForeignKey("QRCodeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Api.Models.ShortLink", "ShortLink") + .WithMany("Events") + .HasForeignKey("ShortLinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Api.Models.Workspace", "Workspace") + .WithMany("Events") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("QRCode"); + + b.Navigation("ShortLink"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Api.Models.Project", b => + { + b.HasOne("Api.Models.Workspace", "Workspace") + .WithMany("Projects") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Api.Models.QRCodeDesign", b => + { + b.HasOne("Api.Models.Asset", "LogoAsset") + .WithMany() + .HasForeignKey("LogoAssetId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Api.Models.Project", "Project") + .WithMany("QRCodeDesigns") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Api.Models.ShortLink", "ShortLink") + .WithMany("QRCodeDesigns") + .HasForeignKey("ShortLinkId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Api.Models.Workspace", "Workspace") + .WithMany("QRCodeDesigns") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LogoAsset"); + + b.Navigation("Project"); + + b.Navigation("ShortLink"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Api.Models.ShortLink", b => + { + b.HasOne("Api.Models.Domain", "Domain") + .WithMany("ShortLinks") + .HasForeignKey("DomainId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Api.Models.Project", "Project") + .WithMany("ShortLinks") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Api.Models.Workspace", "Workspace") + .WithMany("ShortLinks") + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Domain"); + + b.Navigation("Project"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("Api.Models.Workspace", b => + { + b.HasOne("Api.Models.User", "Owner") + .WithMany("Workspaces") + .HasForeignKey("OwnerUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Api.Models.Domain", b => + { + b.Navigation("ShortLinks"); + }); + + modelBuilder.Entity("Api.Models.Project", b => + { + b.Navigation("QRCodeDesigns"); + + b.Navigation("ShortLinks"); + }); + + modelBuilder.Entity("Api.Models.QRCodeDesign", b => + { + b.Navigation("Events"); + }); + + modelBuilder.Entity("Api.Models.ShortLink", b => + { + b.Navigation("Events"); + + b.Navigation("QRCodeDesigns"); + }); + + modelBuilder.Entity("Api.Models.User", b => + { + b.Navigation("Workspaces"); + }); + + modelBuilder.Entity("Api.Models.Workspace", b => + { + b.Navigation("Assets"); + + b.Navigation("Domains"); + + b.Navigation("Events"); + + b.Navigation("Projects"); + + b.Navigation("QRCodeDesigns"); + + b.Navigation("ShortLinks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.cs b/src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.cs new file mode 100644 index 0000000..0009643 --- /dev/null +++ b/src/api/Migrations/20260127193159_AddShortLinksQRCodesEventsAssets.cs @@ -0,0 +1,239 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace api.Migrations +{ + /// + public partial class AddShortLinksQRCodesEventsAssets : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Assets", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + WorkspaceId = table.Column(type: "uuid", nullable: false), + Type = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + StorageKey = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), + Mime = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Size = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP") + }, + constraints: table => + { + table.PrimaryKey("PK_Assets", x => x.Id); + table.ForeignKey( + name: "FK_Assets_Workspaces_WorkspaceId", + column: x => x.WorkspaceId, + principalTable: "Workspaces", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ShortLinks", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + WorkspaceId = table.Column(type: "uuid", nullable: false), + ProjectId = table.Column(type: "uuid", nullable: true), + DomainId = table.Column(type: "uuid", nullable: true), + Slug = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + DestinationUrl = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: false), + Title = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ExpiresAt = table.Column(type: "timestamp with time zone", nullable: true), + PasswordHash = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP") + }, + constraints: table => + { + table.PrimaryKey("PK_ShortLinks", x => x.Id); + table.ForeignKey( + name: "FK_ShortLinks_Domains_DomainId", + column: x => x.DomainId, + principalTable: "Domains", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_ShortLinks_Projects_ProjectId", + column: x => x.ProjectId, + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_ShortLinks_Workspaces_WorkspaceId", + column: x => x.WorkspaceId, + principalTable: "Workspaces", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "QRCodeDesigns", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + WorkspaceId = table.Column(type: "uuid", nullable: false), + ProjectId = table.Column(type: "uuid", nullable: true), + ShortLinkId = table.Column(type: "uuid", nullable: true), + StyleJson = table.Column(type: "jsonb", nullable: false), + LogoAssetId = table.Column(type: "uuid", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP") + }, + constraints: table => + { + table.PrimaryKey("PK_QRCodeDesigns", x => x.Id); + table.ForeignKey( + name: "FK_QRCodeDesigns_Assets_LogoAssetId", + column: x => x.LogoAssetId, + principalTable: "Assets", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_QRCodeDesigns_Projects_ProjectId", + column: x => x.ProjectId, + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_QRCodeDesigns_ShortLinks_ShortLinkId", + column: x => x.ShortLinkId, + principalTable: "ShortLinks", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_QRCodeDesigns_Workspaces_WorkspaceId", + column: x => x.WorkspaceId, + principalTable: "Workspaces", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Events", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + WorkspaceId = table.Column(type: "uuid", nullable: false), + ShortLinkId = table.Column(type: "uuid", nullable: false), + QRCodeId = table.Column(type: "uuid", nullable: true), + Type = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + Timestamp = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + IpHash = table.Column(type: "character varying(64)", maxLength: 64, nullable: true), + UserAgent = table.Column(type: "character varying(512)", maxLength: 512, nullable: true), + Referrer = table.Column(type: "character varying(2048)", maxLength: 2048, nullable: true), + CountryCode = table.Column(type: "character varying(2)", maxLength: 2, nullable: true), + DeviceType = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + DedupeKey = table.Column(type: "character varying(128)", maxLength: 128, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Events", x => x.Id); + table.ForeignKey( + name: "FK_Events_QRCodeDesigns_QRCodeId", + column: x => x.QRCodeId, + principalTable: "QRCodeDesigns", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Events_ShortLinks_ShortLinkId", + column: x => x.ShortLinkId, + principalTable: "ShortLinks", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Events_Workspaces_WorkspaceId", + column: x => x.WorkspaceId, + principalTable: "Workspaces", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Assets_WorkspaceId", + table: "Assets", + column: "WorkspaceId"); + + migrationBuilder.CreateIndex( + name: "IX_Events_QRCodeId", + table: "Events", + column: "QRCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_Events_ShortLinkId_Timestamp", + table: "Events", + columns: new[] { "ShortLinkId", "Timestamp" }); + + migrationBuilder.CreateIndex( + name: "IX_Events_Timestamp", + table: "Events", + column: "Timestamp"); + + migrationBuilder.CreateIndex( + name: "IX_Events_WorkspaceId_Timestamp", + table: "Events", + columns: new[] { "WorkspaceId", "Timestamp" }); + + migrationBuilder.CreateIndex( + name: "IX_QRCodeDesigns_LogoAssetId", + table: "QRCodeDesigns", + column: "LogoAssetId"); + + migrationBuilder.CreateIndex( + name: "IX_QRCodeDesigns_ProjectId", + table: "QRCodeDesigns", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_QRCodeDesigns_ShortLinkId", + table: "QRCodeDesigns", + column: "ShortLinkId"); + + migrationBuilder.CreateIndex( + name: "IX_QRCodeDesigns_WorkspaceId", + table: "QRCodeDesigns", + column: "WorkspaceId"); + + migrationBuilder.CreateIndex( + name: "IX_ShortLinks_DomainId_Slug", + table: "ShortLinks", + columns: new[] { "DomainId", "Slug" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ShortLinks_ProjectId", + table: "ShortLinks", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_ShortLinks_WorkspaceId", + table: "ShortLinks", + column: "WorkspaceId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Events"); + + migrationBuilder.DropTable( + name: "QRCodeDesigns"); + + migrationBuilder.DropTable( + name: "Assets"); + + migrationBuilder.DropTable( + name: "ShortLinks"); + } + } +} diff --git a/src/api/Models/Asset.cs b/src/api/Models/Asset.cs new file mode 100644 index 0000000..b7dc7e9 --- /dev/null +++ b/src/api/Models/Asset.cs @@ -0,0 +1,20 @@ +namespace Api.Models; + +public enum AssetType +{ + Logo +} + +public class Asset +{ + public Guid Id { get; set; } + public Guid WorkspaceId { get; set; } + public AssetType Type { get; set; } + public required string StorageKey { get; set; } + public required string Mime { get; set; } + public long Size { get; set; } + public DateTime CreatedAt { get; set; } + + // Navigation properties + public Workspace Workspace { get; set; } = null!; +} diff --git a/src/api/Models/Event.cs b/src/api/Models/Event.cs new file mode 100644 index 0000000..abb9e25 --- /dev/null +++ b/src/api/Models/Event.cs @@ -0,0 +1,28 @@ +namespace Api.Models; + +public enum EventType +{ + Click, + Scan +} + +public class Event +{ + public long Id { get; set; } + public Guid WorkspaceId { get; set; } + public Guid ShortLinkId { get; set; } + public Guid? QRCodeId { get; set; } + public EventType Type { get; set; } + public DateTime Timestamp { get; set; } + public string? IpHash { get; set; } + public string? UserAgent { get; set; } + public string? Referrer { get; set; } + public string? CountryCode { get; set; } + public string? DeviceType { get; set; } + public string? DedupeKey { get; set; } + + // Navigation properties + public Workspace Workspace { get; set; } = null!; + public ShortLink ShortLink { get; set; } = null!; + public QRCodeDesign? QRCode { get; set; } +} diff --git a/src/api/Models/QRCodeDesign.cs b/src/api/Models/QRCodeDesign.cs new file mode 100644 index 0000000..f4ccc7f --- /dev/null +++ b/src/api/Models/QRCodeDesign.cs @@ -0,0 +1,20 @@ +namespace Api.Models; + +public class QRCodeDesign +{ + public Guid Id { get; set; } + public Guid WorkspaceId { get; set; } + public Guid? ProjectId { get; set; } + public Guid? ShortLinkId { get; set; } + public required string StyleJson { get; set; } + public Guid? LogoAssetId { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + + // Navigation properties + public Workspace Workspace { get; set; } = null!; + public Project? Project { get; set; } + public ShortLink? ShortLink { get; set; } + public Asset? LogoAsset { get; set; } + public ICollection Events { get; set; } = []; +} diff --git a/src/api/Models/ShortLink.cs b/src/api/Models/ShortLink.cs new file mode 100644 index 0000000..80c064a --- /dev/null +++ b/src/api/Models/ShortLink.cs @@ -0,0 +1,30 @@ +namespace Api.Models; + +public enum ShortLinkStatus +{ + Active, + Disabled +} + +public class ShortLink +{ + public Guid Id { get; set; } + public Guid WorkspaceId { get; set; } + public Guid? ProjectId { get; set; } + public Guid? DomainId { get; set; } + public required string Slug { get; set; } + public required string DestinationUrl { get; set; } + public string? Title { get; set; } + public ShortLinkStatus Status { get; set; } = ShortLinkStatus.Active; + public DateTime? ExpiresAt { get; set; } + public string? PasswordHash { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + + // Navigation properties + public Workspace Workspace { get; set; } = null!; + public Project? Project { get; set; } + public Domain? Domain { get; set; } + public ICollection QRCodeDesigns { get; set; } = []; + public ICollection Events { get; set; } = []; +}