diff --git a/src/Giveaways.Data/AppDbContext.cs b/src/Giveaways.Data/AppDbContext.cs
index 0c02f52..ecad024 100644
--- a/src/Giveaways.Data/AppDbContext.cs
+++ b/src/Giveaways.Data/AppDbContext.cs
@@ -11,6 +11,6 @@ namespace Giveaways.Data;
/// The options for this context.
public class AppDbContext(DbContextOptions options) : DbContext(options)
{
- // TODO: Configure the database context.
- // Learn more at the https://learn.microsoft.com/ef/core.
+ public DbSet Giveaways { get; set; }
+ public DbSet GiveawayParticipants { get; set; }
}
diff --git a/src/Giveaways.Data/Migrations/20240624181527_InitialCreate.Designer.cs b/src/Giveaways.Data/Migrations/20240624181527_InitialCreate.Designer.cs
new file mode 100644
index 0000000..51b0911
--- /dev/null
+++ b/src/Giveaways.Data/Migrations/20240624181527_InitialCreate.Designer.cs
@@ -0,0 +1,88 @@
+//
+using System;
+using Giveaways.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Giveaways.Data.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20240624181527_InitialCreate")]
+ partial class InitialCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("Giveaways.Data.Giveaway", b =>
+ {
+ b.Property("MessageId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChannelId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("GuildId")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxWinners")
+ .HasColumnType("INTEGER");
+
+ b.Property("Prize")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("MessageId");
+
+ b.ToTable("Giveaways");
+ });
+
+ modelBuilder.Entity("Giveaways.Data.GiveawayParticipant", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("GiveawayId")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsWinner")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "GiveawayId");
+
+ b.HasIndex("GiveawayId");
+
+ b.ToTable("GiveawayParticipants");
+ });
+
+ modelBuilder.Entity("Giveaways.Data.GiveawayParticipant", b =>
+ {
+ b.HasOne("Giveaways.Data.Giveaway", "Giveaway")
+ .WithMany("Participants")
+ .HasForeignKey("GiveawayId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Giveaway");
+ });
+
+ modelBuilder.Entity("Giveaways.Data.Giveaway", b =>
+ {
+ b.Navigation("Participants");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Giveaways.Data/Migrations/20240624181527_InitialCreate.cs b/src/Giveaways.Data/Migrations/20240624181527_InitialCreate.cs
new file mode 100644
index 0000000..b44cf53
--- /dev/null
+++ b/src/Giveaways.Data/Migrations/20240624181527_InitialCreate.cs
@@ -0,0 +1,66 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Giveaways.Data.Migrations
+{
+ ///
+ public partial class InitialCreate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Giveaways",
+ columns: table => new
+ {
+ MessageId = table.Column(type: "INTEGER", nullable: false),
+ ChannelId = table.Column(type: "INTEGER", nullable: false),
+ GuildId = table.Column(type: "INTEGER", nullable: false),
+ Prize = table.Column(type: "TEXT", nullable: false),
+ MaxWinners = table.Column(type: "INTEGER", nullable: false),
+ ExpiresAt = table.Column(type: "TEXT", nullable: false),
+ Status = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Giveaways", x => x.MessageId);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "GiveawayParticipants",
+ columns: table => new
+ {
+ UserId = table.Column(type: "INTEGER", nullable: false),
+ GiveawayId = table.Column(type: "INTEGER", nullable: false),
+ IsWinner = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_GiveawayParticipants", x => new { x.UserId, x.GiveawayId });
+ table.ForeignKey(
+ name: "FK_GiveawayParticipants_Giveaways_GiveawayId",
+ column: x => x.GiveawayId,
+ principalTable: "Giveaways",
+ principalColumn: "MessageId",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_GiveawayParticipants_GiveawayId",
+ table: "GiveawayParticipants",
+ column: "GiveawayId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "GiveawayParticipants");
+
+ migrationBuilder.DropTable(
+ name: "Giveaways");
+ }
+ }
+}
diff --git a/src/Giveaways.Data/Migrations/AppDbContextModelSnapshot.cs b/src/Giveaways.Data/Migrations/AppDbContextModelSnapshot.cs
new file mode 100644
index 0000000..4c81856
--- /dev/null
+++ b/src/Giveaways.Data/Migrations/AppDbContextModelSnapshot.cs
@@ -0,0 +1,85 @@
+//
+using System;
+using Giveaways.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Giveaways.Data.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ partial class AppDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("Giveaways.Data.Giveaway", b =>
+ {
+ b.Property("MessageId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChannelId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("TEXT");
+
+ b.Property("GuildId")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxWinners")
+ .HasColumnType("INTEGER");
+
+ b.Property("Prize")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("Status")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("MessageId");
+
+ b.ToTable("Giveaways");
+ });
+
+ modelBuilder.Entity("Giveaways.Data.GiveawayParticipant", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("GiveawayId")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsWinner")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "GiveawayId");
+
+ b.HasIndex("GiveawayId");
+
+ b.ToTable("GiveawayParticipants");
+ });
+
+ modelBuilder.Entity("Giveaways.Data.GiveawayParticipant", b =>
+ {
+ b.HasOne("Giveaways.Data.Giveaway", "Giveaway")
+ .WithMany("Participants")
+ .HasForeignKey("GiveawayId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Giveaway");
+ });
+
+ modelBuilder.Entity("Giveaways.Data.Giveaway", b =>
+ {
+ b.Navigation("Participants");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Giveaways.Data/Models/Giveaway.cs b/src/Giveaways.Data/Models/Giveaway.cs
new file mode 100644
index 0000000..0f32d34
--- /dev/null
+++ b/src/Giveaways.Data/Models/Giveaway.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace Giveaways.Data;
+
+///
+/// Represents a giveaway hosted by a user.
+///
+public class Giveaway
+{
+ ///
+ /// Gets the message identifier of the giveaway message.
+ ///
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public required ulong MessageId { get; init; }
+
+ ///
+ /// Gets the channel identifier where this giveaway was hosted.
+ ///
+ public required ulong ChannelId { get; init; }
+
+ ///
+ /// Gets the guild identifier where this giveaway was hosted.
+ ///
+ public required ulong GuildId { get; init; }
+
+ ///
+ /// Gets or sets the prize of this giveaway.
+ ///
+ public required string Prize { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of winners for this giveaway.
+ ///
+ public required int MaxWinners { get; set; }
+
+ ///
+ /// Gets or sets the expiration date and time for this giveaway.
+ ///
+ public required DateTime ExpiresAt { get; set; }
+
+ ///
+ /// Gets or sets the current status of this giveaway.
+ ///
+ public GiveawayStatus Status { get; set; } = GiveawayStatus.Active;
+
+ ///
+ /// Gets the list of participants for this giveaway.
+ ///
+ public List Participants { get; } = [];
+}
diff --git a/src/Giveaways.Data/Models/GiveawayParticipant.cs b/src/Giveaways.Data/Models/GiveawayParticipant.cs
new file mode 100644
index 0000000..7fae017
--- /dev/null
+++ b/src/Giveaways.Data/Models/GiveawayParticipant.cs
@@ -0,0 +1,32 @@
+using System.ComponentModel.DataAnnotations.Schema;
+using Microsoft.EntityFrameworkCore;
+
+namespace Giveaways.Data;
+
+///
+/// Represents a participant of a giveaway.
+///
+[PrimaryKey(nameof(UserId), nameof(GiveawayId))]
+public class GiveawayParticipant
+{
+ ///
+ /// Gets or sets the user ID of the participant.
+ ///
+ [DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public ulong UserId { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the participant is a winner.
+ ///
+ public bool IsWinner { get; set; } = false;
+
+ ///
+ /// Gets or sets the ID of the giveaway the participant is associated with.
+ ///
+ public ulong GiveawayId { get; set; }
+
+ ///
+ /// Gets or sets the reference to the giveaway the participant is associated with.
+ ///
+ public Giveaway Giveaway { get; set; } = null!;
+}
diff --git a/src/Giveaways.Data/Models/GiveawayStatus.cs b/src/Giveaways.Data/Models/GiveawayStatus.cs
new file mode 100644
index 0000000..afd69fc
--- /dev/null
+++ b/src/Giveaways.Data/Models/GiveawayStatus.cs
@@ -0,0 +1,22 @@
+namespace Giveaways;
+
+///
+/// Represents the status of a giveaway.
+///
+public enum GiveawayStatus
+{
+ ///
+ /// The giveaway is currently active and participants can still enter.
+ ///
+ Active,
+
+ ///
+ /// The giveaway is temporarily suspended and not accepting new entries.
+ ///
+ Suspended,
+
+ ///
+ /// The giveaway has ended, and winners have been selected.
+ ///
+ Ended,
+}