diff --git a/DysonNetwork.Sphere/Account/Notification.cs b/DysonNetwork.Sphere/Account/Notification.cs
new file mode 100644
index 0000000..7015d4c
--- /dev/null
+++ b/DysonNetwork.Sphere/Account/Notification.cs
@@ -0,0 +1,41 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
+using Microsoft.EntityFrameworkCore;
+using NodaTime;
+
+namespace DysonNetwork.Sphere.Account;
+
+public class Notification : ModelBase
+{
+    public Guid Id { get; set; } = Guid.NewGuid();
+    [MaxLength(1024)] public string? Title { get; set; }
+    [MaxLength(2048)] public string? Subtitle { get; set; }
+    [MaxLength(4096)] public string? Content { get; set; }
+    [Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
+    public int Priority { get; set; } = 10;
+    public Instant? ViewedAt { get; set; }
+
+    public long AccountId { get; set; }
+    [JsonIgnore] public Account Account { get; set; } = null!;
+}
+
+public enum NotificationPushProvider
+{
+    Apple,
+    Google
+}
+
+[Index(nameof(DeviceId), IsUnique = true)]
+[Index(nameof(DeviceToken), IsUnique = true)]
+public class NotificationPushSubscription : ModelBase
+{
+    public Guid Id { get; set; } = Guid.NewGuid();
+    [MaxLength(4096)] public string DeviceId { get; set; } = null!;
+    [MaxLength(4096)] public string DeviceToken { get; set; } = null!;
+    public NotificationPushProvider Provider { get; set; }
+    public Instant? LastUsedAt { get; set; }
+
+    public long AccountId { get; set; }
+    [JsonIgnore] public Account Account { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Account/NotificationService.cs b/DysonNetwork.Sphere/Account/NotificationService.cs
new file mode 100644
index 0000000..0082f1f
--- /dev/null
+++ b/DysonNetwork.Sphere/Account/NotificationService.cs
@@ -0,0 +1,182 @@
+using CorePush.Apple;
+using CorePush.Firebase;
+using Microsoft.EntityFrameworkCore;
+using NodaTime;
+
+namespace DysonNetwork.Sphere.Account;
+
+public class NotificationService
+{
+    private readonly AppDatabase _db;
+    private readonly ILogger<NotificationService> _logger;
+    private readonly FirebaseSender? _fcm;
+    private readonly ApnSender? _apns;
+
+    public NotificationService(
+        AppDatabase db,
+        IConfiguration cfg,
+        IHttpClientFactory clientFactory,
+        ILogger<NotificationService> logger
+    )
+    {
+        _db = db;
+        _logger = logger;
+
+        var cfgSection = cfg.GetSection("Notifications:Push");
+
+        // Set up the firebase push notification
+        var fcmConfig = cfgSection.GetValue<string>("Google");
+        if (fcmConfig != null)
+            _fcm = new FirebaseSender(File.ReadAllText(fcmConfig), clientFactory.CreateClient());
+        // Set up the apple push notification service
+        var apnsCert = cfgSection.GetValue<string>("Apple:PrivateKey");
+        if (apnsCert != null)
+            _apns = new ApnSender(new ApnSettings
+            {
+                P8PrivateKey = File.ReadAllText(apnsCert),
+                P8PrivateKeyId = cfgSection.GetValue<string>("Apple:PrivateKeyId"),
+                TeamId = cfgSection.GetValue<string>("Apple:TeamId"),
+                AppBundleIdentifier = cfgSection.GetValue<string>("Apple:BundleIdentifier"),
+                ServerType = cfgSection.GetValue<bool>("Production")
+                    ? ApnServerType.Production
+                    : ApnServerType.Development
+            }, clientFactory.CreateClient());
+    }
+
+    public async Task<NotificationPushSubscription> SubscribePushNotification(
+        Account account,
+        NotificationPushProvider provider,
+        string deviceId,
+        string deviceToken
+    )
+    {
+        var existingSubscription = await _db.NotificationPushSubscriptions
+            .Where(s => s.AccountId == account.Id)
+            .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken)
+            .FirstOrDefaultAsync();
+
+        if (existingSubscription != null)
+        {
+            // Reset these audit fields to renew the lifecycle of this device token
+            existingSubscription.CreatedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
+            existingSubscription.UpdatedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
+            _db.Update(existingSubscription);
+            await _db.SaveChangesAsync();
+            return existingSubscription;
+        }
+
+        var subscription = new NotificationPushSubscription
+        {
+            DeviceId = deviceId,
+            DeviceToken = deviceToken,
+            Provider = provider,
+            Account = account,
+            AccountId = account.Id,
+        };
+
+        _db.Add(subscription);
+        await _db.SaveChangesAsync();
+
+        return subscription;
+    }
+
+    public async Task<Notification> SendNotification(
+        Account account,
+        string? title = null,
+        string? subtitle = null,
+        string? content = null,
+        Dictionary<string, object>? meta = null,
+        bool isSilent = false
+    )
+    {
+        if (title is null && subtitle is null && content is null)
+        {
+            throw new ArgumentException("Unable to send notification that completely empty.");
+        }
+
+        var notification = new Notification
+        {
+            Title = title,
+            Subtitle = subtitle,
+            Content = content,
+            Meta = meta,
+            Account = account,
+            AccountId = account.Id,
+        };
+
+        _db.Add(notification);
+        await _db.SaveChangesAsync();
+
+#pragma warning disable CS4014
+        if (!isSilent) DeliveryNotification(notification);
+#pragma warning restore CS4014
+
+        return notification;
+    }
+
+    public async Task DeliveryNotification(Notification notification)
+    {
+        // TODO send websocket
+
+        // Pushing the notification
+        var subscribers = await _db.NotificationPushSubscriptions
+            .Where(s => s.AccountId == notification.AccountId)
+            .ToListAsync();
+
+        var tasks = new List<Task>();
+        foreach (var subscriber in subscribers)
+        {
+            tasks.Add(_PushSingleNotification(notification, subscriber));
+        }
+
+        await Task.WhenAll(tasks);
+    }
+
+    private async Task _PushSingleNotification(Notification notification, NotificationPushSubscription subscription)
+    {
+        switch (subscription.Provider)
+        {
+            case NotificationPushProvider.Google:
+                if (_fcm == null)
+                    throw new InvalidOperationException("The firebase cloud messaging is not initialized.");
+                await _fcm.SendAsync(new
+                {
+                    message = new
+                    {
+                        token = subscription.DeviceToken,
+                        notification = new
+                        {
+                            title = notification.Title,
+                            body = string.Join("\n", notification.Subtitle, notification.Content),
+                        },
+                        data = notification.Meta
+                    }
+                });
+                break;
+            case NotificationPushProvider.Apple:
+                if (_apns == null)
+                    throw new InvalidOperationException("The apple notification push service is not initialized.");
+                await _apns.SendAsync(new
+                    {
+                        apns = new
+                        {
+                            alert = new
+                            {
+                                title = notification.Title,
+                                subtitle = notification.Subtitle,
+                                content = notification.Content,
+                            }
+                        },
+                        meta = notification.Meta,
+                    },
+                    deviceToken: subscription.DeviceToken,
+                    apnsId: notification.Id.ToString(),
+                    apnsPriority: notification.Priority,
+                    apnPushType: ApnPushType.Alert
+                );
+                break;
+            default:
+                throw new InvalidOperationException($"Provider not supported: {subscription.Provider}");
+        }
+    }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs
index c0f6f2a..cfb32c2 100644
--- a/DysonNetwork.Sphere/AppDatabase.cs
+++ b/DysonNetwork.Sphere/AppDatabase.cs
@@ -26,6 +26,8 @@ public class AppDatabase(
     public DbSet<Account.Relationship> AccountRelationships { get; set; }
     public DbSet<Auth.Session> AuthSessions { get; set; }
     public DbSet<Auth.Challenge> AuthChallenges { get; set; }
+    public DbSet<Account.Notification> Notifications { get; set; }
+    public DbSet<Account.NotificationPushSubscription> NotificationPushSubscriptions { get; set; }
     public DbSet<Storage.CloudFile> Files { get; set; }
     public DbSet<Post.Publisher> Publishers { get; set; }
     public DbSet<Post.PublisherMember> PublisherMembers { get; set; }
diff --git a/DysonNetwork.Sphere/Auth/Session.cs b/DysonNetwork.Sphere/Auth/Session.cs
index 373972a..508c414 100644
--- a/DysonNetwork.Sphere/Auth/Session.cs
+++ b/DysonNetwork.Sphere/Auth/Session.cs
@@ -29,6 +29,7 @@ public class Challenge : ModelBase
     [MaxLength(256)] public string? DeviceId { get; set; }
     [MaxLength(1024)] public string? Nonce { get; set; }
 
+    public long AccountId { get; set; }
     [JsonIgnore] public Account.Account Account { get; set; } = null!;
 
     public Challenge Normalize()
diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
index c0a3d83..66bdb9c 100644
--- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
+++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
@@ -13,6 +13,7 @@
         <PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
         <PackageReference Include="Casbin.NET" Version="2.12.0" />
         <PackageReference Include="Casbin.NET.Adapter.EFCore" Version="2.5.0" />
+        <PackageReference Include="CorePush" Version="4.3.0" />
         <PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
         <PackageReference Include="FFMpegCore" Version="5.2.0" />
         <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.4" />
diff --git a/DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.Designer.cs b/DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.Designer.cs
new file mode 100644
index 0000000..814e2b0
--- /dev/null
+++ b/DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.Designer.cs
@@ -0,0 +1,1406 @@
+// <auto-generated />
+using System;
+using System.Collections.Generic;
+using DysonNetwork.Sphere;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using NodaTime;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace DysonNetwork.Sphere.Migrations
+{
+    [DbContext(typeof(AppDatabase))]
+    [Migration("20250427154356_AddNotification")]
+    partial class AddNotification
+    {
+        /// <inheritdoc />
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "9.0.3")
+                .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<bool>("IsSuperuser")
+                        .HasColumnType("boolean")
+                        .HasColumnName("is_superuser");
+
+                    b.Property<string>("Language")
+                        .IsRequired()
+                        .HasMaxLength(32)
+                        .HasColumnType("character varying(32)")
+                        .HasColumnName("language");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("name");
+
+                    b.Property<string>("Nick")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("nick");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_accounts");
+
+                    b.HasIndex("Name")
+                        .IsUnique()
+                        .HasDatabaseName("ix_accounts_name");
+
+                    b.ToTable("accounts", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("Secret")
+                        .HasColumnType("text")
+                        .HasColumnName("secret");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("integer")
+                        .HasColumnName("type");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_account_auth_factors");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_account_auth_factors_account_id");
+
+                    b.ToTable("account_auth_factors", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<string>("Content")
+                        .IsRequired()
+                        .HasMaxLength(1024)
+                        .HasColumnType("character varying(1024)")
+                        .HasColumnName("content");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("integer")
+                        .HasColumnName("type");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.Property<Instant?>("VerifiedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("verified_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_account_contacts");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_account_contacts_account_id");
+
+                    b.ToTable("account_contacts", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasColumnName("id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<string>("Content")
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("content");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<Dictionary<string, object>>("Meta")
+                        .HasColumnType("jsonb")
+                        .HasColumnName("meta");
+
+                    b.Property<int>("Priority")
+                        .HasColumnType("integer")
+                        .HasColumnName("priority");
+
+                    b.Property<string>("Subtitle")
+                        .HasMaxLength(2048)
+                        .HasColumnType("character varying(2048)")
+                        .HasColumnName("subtitle");
+
+                    b.Property<string>("Title")
+                        .HasMaxLength(1024)
+                        .HasColumnType("character varying(1024)")
+                        .HasColumnName("title");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.Property<Instant?>("ViewedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("viewed_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_notifications");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_notifications_account_id");
+
+                    b.ToTable("notifications", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasColumnName("id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("DeviceId")
+                        .IsRequired()
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("device_id");
+
+                    b.Property<string>("DeviceToken")
+                        .IsRequired()
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("device_token");
+
+                    b.Property<Instant?>("LastUsedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("last_used_at");
+
+                    b.Property<int>("Provider")
+                        .HasColumnType("integer")
+                        .HasColumnName("provider");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_notification_push_subscriptions");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_notification_push_subscriptions_account_id");
+
+                    b.HasIndex("DeviceId")
+                        .IsUnique()
+                        .HasDatabaseName("ix_notification_push_subscriptions_device_id");
+
+                    b.HasIndex("DeviceToken")
+                        .IsUnique()
+                        .HasDatabaseName("ix_notification_push_subscriptions_device_token");
+
+                    b.ToTable("notification_push_subscriptions", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
+                {
+                    b.Property<long>("Id")
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    b.Property<string>("BackgroundId")
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("background_id");
+
+                    b.Property<string>("Bio")
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("bio");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("FirstName")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("first_name");
+
+                    b.Property<string>("LastName")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("last_name");
+
+                    b.Property<string>("MiddleName")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("middle_name");
+
+                    b.Property<string>("PictureId")
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("picture_id");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_account_profiles");
+
+                    b.HasIndex("BackgroundId")
+                        .HasDatabaseName("ix_account_profiles_background_id");
+
+                    b.HasIndex("PictureId")
+                        .HasDatabaseName("ix_account_profiles_picture_id");
+
+                    b.ToTable("account_profiles", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b =>
+                {
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<long>("RelatedId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("related_id");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<Instant?>("ExpiredAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("expired_at");
+
+                    b.Property<int>("Status")
+                        .HasColumnType("integer")
+                        .HasColumnName("status");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("AccountId", "RelatedId")
+                        .HasName("pk_account_relationships");
+
+                    b.HasIndex("RelatedId")
+                        .HasDatabaseName("ix_account_relationships_related_id");
+
+                    b.ToTable("account_relationships", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasColumnName("id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<List<string>>("Audiences")
+                        .IsRequired()
+                        .HasColumnType("jsonb")
+                        .HasColumnName("audiences");
+
+                    b.Property<List<long>>("BlacklistFactors")
+                        .IsRequired()
+                        .HasColumnType("jsonb")
+                        .HasColumnName("blacklist_factors");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("DeviceId")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("device_id");
+
+                    b.Property<Instant?>("ExpiredAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("expired_at");
+
+                    b.Property<string>("IpAddress")
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("ip_address");
+
+                    b.Property<string>("Nonce")
+                        .HasMaxLength(1024)
+                        .HasColumnType("character varying(1024)")
+                        .HasColumnName("nonce");
+
+                    b.Property<List<string>>("Scopes")
+                        .IsRequired()
+                        .HasColumnType("jsonb")
+                        .HasColumnName("scopes");
+
+                    b.Property<int>("StepRemain")
+                        .HasColumnType("integer")
+                        .HasColumnName("step_remain");
+
+                    b.Property<int>("StepTotal")
+                        .HasColumnType("integer")
+                        .HasColumnName("step_total");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.Property<string>("UserAgent")
+                        .HasMaxLength(512)
+                        .HasColumnType("character varying(512)")
+                        .HasColumnName("user_agent");
+
+                    b.HasKey("Id")
+                        .HasName("pk_auth_challenges");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_auth_challenges_account_id");
+
+                    b.ToTable("auth_challenges", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasColumnName("id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<Guid>("ChallengeId")
+                        .HasColumnType("uuid")
+                        .HasColumnName("challenge_id");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<Instant?>("ExpiredAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("expired_at");
+
+                    b.Property<Instant?>("LastGrantedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("last_granted_at");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_auth_sessions");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_auth_sessions_account_id");
+
+                    b.HasIndex("ChallengeId")
+                        .HasDatabaseName("ix_auth_sessions_challenge_id");
+
+                    b.ToTable("auth_sessions", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<string>("Content")
+                        .HasColumnType("text")
+                        .HasColumnName("content");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("Description")
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("description");
+
+                    b.Property<int>("Downvotes")
+                        .HasColumnType("integer")
+                        .HasColumnName("downvotes");
+
+                    b.Property<Instant?>("EditedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("edited_at");
+
+                    b.Property<long?>("ForwardedPostId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("forwarded_post_id");
+
+                    b.Property<string>("Language")
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("language");
+
+                    b.Property<Dictionary<string, object>>("Meta")
+                        .HasColumnType("jsonb")
+                        .HasColumnName("meta");
+
+                    b.Property<Instant?>("PublishedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("published_at");
+
+                    b.Property<long>("PublisherId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("publisher_id");
+
+                    b.Property<long?>("RepliedPostId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("replied_post_id");
+
+                    b.Property<long?>("ThreadedPostId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("threaded_post_id");
+
+                    b.Property<string>("Title")
+                        .HasMaxLength(1024)
+                        .HasColumnType("character varying(1024)")
+                        .HasColumnName("title");
+
+                    b.Property<int>("Type")
+                        .HasColumnType("integer")
+                        .HasColumnName("type");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.Property<int>("Upvotes")
+                        .HasColumnType("integer")
+                        .HasColumnName("upvotes");
+
+                    b.Property<int>("ViewsTotal")
+                        .HasColumnType("integer")
+                        .HasColumnName("views_total");
+
+                    b.Property<int>("ViewsUnique")
+                        .HasColumnType("integer")
+                        .HasColumnName("views_unique");
+
+                    b.Property<int>("Visibility")
+                        .HasColumnType("integer")
+                        .HasColumnName("visibility");
+
+                    b.HasKey("Id")
+                        .HasName("pk_posts");
+
+                    b.HasIndex("ForwardedPostId")
+                        .HasDatabaseName("ix_posts_forwarded_post_id");
+
+                    b.HasIndex("PublisherId")
+                        .HasDatabaseName("ix_posts_publisher_id");
+
+                    b.HasIndex("RepliedPostId")
+                        .HasDatabaseName("ix_posts_replied_post_id");
+
+                    b.HasIndex("ThreadedPostId")
+                        .IsUnique()
+                        .HasDatabaseName("ix_posts_threaded_post_id");
+
+                    b.ToTable("posts", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCategory", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("Name")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("name");
+
+                    b.Property<string>("Slug")
+                        .IsRequired()
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("slug");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_post_categories");
+
+                    b.ToTable("post_categories", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("Description")
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("description");
+
+                    b.Property<string>("Name")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("name");
+
+                    b.Property<long>("PublisherId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("publisher_id");
+
+                    b.Property<string>("Slug")
+                        .IsRequired()
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("slug");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_post_collections");
+
+                    b.HasIndex("PublisherId")
+                        .HasDatabaseName("ix_post_collections_publisher_id");
+
+                    b.ToTable("post_collections", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<int>("Attitude")
+                        .HasColumnType("integer")
+                        .HasColumnName("attitude");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<long>("PostId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("post_id");
+
+                    b.Property<string>("Symbol")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("symbol");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_post_reactions");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_post_reactions_account_id");
+
+                    b.HasIndex("PostId")
+                        .HasDatabaseName("ix_post_reactions_post_id");
+
+                    b.ToTable("post_reactions", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PostTag", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("Name")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("name");
+
+                    b.Property<string>("Slug")
+                        .IsRequired()
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("slug");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_post_tags");
+
+                    b.ToTable("post_tags", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b =>
+                {
+                    b.Property<long>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("bigint")
+                        .HasColumnName("id");
+
+                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
+
+                    b.Property<long?>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<string>("BackgroundId")
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("background_id");
+
+                    b.Property<string>("Bio")
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("bio");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("name");
+
+                    b.Property<string>("Nick")
+                        .IsRequired()
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("nick");
+
+                    b.Property<string>("PictureId")
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("picture_id");
+
+                    b.Property<int>("PublisherType")
+                        .HasColumnType("integer")
+                        .HasColumnName("publisher_type");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_publishers");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_publishers_account_id");
+
+                    b.HasIndex("BackgroundId")
+                        .HasDatabaseName("ix_publishers_background_id");
+
+                    b.HasIndex("Name")
+                        .IsUnique()
+                        .HasDatabaseName("ix_publishers_name");
+
+                    b.HasIndex("PictureId")
+                        .HasDatabaseName("ix_publishers_picture_id");
+
+                    b.ToTable("publishers", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b =>
+                {
+                    b.Property<long>("PublisherId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("publisher_id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<Instant?>("JoinedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("joined_at");
+
+                    b.Property<int>("Role")
+                        .HasColumnType("integer")
+                        .HasColumnName("role");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("PublisherId", "AccountId")
+                        .HasName("pk_publisher_members");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_publisher_members_account_id");
+
+                    b.ToTable("publisher_members", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
+                {
+                    b.Property<string>("Id")
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("Description")
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("description");
+
+                    b.Property<Instant?>("ExpiredAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("expired_at");
+
+                    b.Property<Dictionary<string, object>>("FileMeta")
+                        .HasColumnType("jsonb")
+                        .HasColumnName("file_meta");
+
+                    b.Property<string>("Hash")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("hash");
+
+                    b.Property<string>("MimeType")
+                        .HasMaxLength(256)
+                        .HasColumnType("character varying(256)")
+                        .HasColumnName("mime_type");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasMaxLength(1024)
+                        .HasColumnType("character varying(1024)")
+                        .HasColumnName("name");
+
+                    b.Property<long?>("PostId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("post_id");
+
+                    b.Property<long>("Size")
+                        .HasColumnType("bigint")
+                        .HasColumnName("size");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.Property<Instant?>("UploadedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("uploaded_at");
+
+                    b.Property<string>("UploadedTo")
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
+                        .HasColumnName("uploaded_to");
+
+                    b.Property<int>("UsedCount")
+                        .HasColumnType("integer")
+                        .HasColumnName("used_count");
+
+                    b.Property<Dictionary<string, object>>("UserMeta")
+                        .HasColumnType("jsonb")
+                        .HasColumnName("user_meta");
+
+                    b.HasKey("Id")
+                        .HasName("pk_files");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_files_account_id");
+
+                    b.HasIndex("PostId")
+                        .HasDatabaseName("ix_files_post_id");
+
+                    b.ToTable("files", (string)null);
+                });
+
+            modelBuilder.Entity("PostPostCategory", b =>
+                {
+                    b.Property<long>("CategoriesId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("categories_id");
+
+                    b.Property<long>("PostsId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("posts_id");
+
+                    b.HasKey("CategoriesId", "PostsId")
+                        .HasName("pk_post_category_links");
+
+                    b.HasIndex("PostsId")
+                        .HasDatabaseName("ix_post_category_links_posts_id");
+
+                    b.ToTable("post_category_links", (string)null);
+                });
+
+            modelBuilder.Entity("PostPostCollection", b =>
+                {
+                    b.Property<long>("CollectionsId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("collections_id");
+
+                    b.Property<long>("PostsId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("posts_id");
+
+                    b.HasKey("CollectionsId", "PostsId")
+                        .HasName("pk_post_collection_links");
+
+                    b.HasIndex("PostsId")
+                        .HasDatabaseName("ix_post_collection_links_posts_id");
+
+                    b.ToTable("post_collection_links", (string)null);
+                });
+
+            modelBuilder.Entity("PostPostTag", b =>
+                {
+                    b.Property<long>("PostsId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("posts_id");
+
+                    b.Property<long>("TagsId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("tags_id");
+
+                    b.HasKey("PostsId", "TagsId")
+                        .HasName("pk_post_tag_links");
+
+                    b.HasIndex("TagsId")
+                        .HasDatabaseName("ix_post_tag_links_tags_id");
+
+                    b.ToTable("post_tag_links", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountAuthFactor", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany("AuthFactors")
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_account_auth_factors_accounts_account_id");
+
+                    b.Navigation("Account");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.AccountContact", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany("Contacts")
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_account_contacts_accounts_account_id");
+
+                    b.Navigation("Account");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_notifications_accounts_account_id");
+
+                    b.Navigation("Account");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id");
+
+                    b.Navigation("Account");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background")
+                        .WithMany()
+                        .HasForeignKey("BackgroundId")
+                        .HasConstraintName("fk_account_profiles_files_background_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithOne("Profile")
+                        .HasForeignKey("DysonNetwork.Sphere.Account.Profile", "Id")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_account_profiles_accounts_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture")
+                        .WithMany()
+                        .HasForeignKey("PictureId")
+                        .HasConstraintName("fk_account_profiles_files_picture_id");
+
+                    b.Navigation("Account");
+
+                    b.Navigation("Background");
+
+                    b.Navigation("Picture");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Relationship", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany("OutgoingRelationships")
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_account_relationships_accounts_account_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Related")
+                        .WithMany("IncomingRelationships")
+                        .HasForeignKey("RelatedId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_account_relationships_accounts_related_id");
+
+                    b.Navigation("Account");
+
+                    b.Navigation("Related");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany("Challenges")
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_auth_challenges_accounts_account_id");
+
+                    b.Navigation("Account");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Auth.Session", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany("Sessions")
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_auth_sessions_accounts_account_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Auth.Challenge", "Challenge")
+                        .WithMany()
+                        .HasForeignKey("ChallengeId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_auth_sessions_auth_challenges_challenge_id");
+
+                    b.Navigation("Account");
+
+                    b.Navigation("Challenge");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", "ForwardedPost")
+                        .WithMany()
+                        .HasForeignKey("ForwardedPostId")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .HasConstraintName("fk_posts_posts_forwarded_post_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
+                        .WithMany("Posts")
+                        .HasForeignKey("PublisherId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_posts_publishers_publisher_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", "RepliedPost")
+                        .WithMany()
+                        .HasForeignKey("RepliedPostId")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .HasConstraintName("fk_posts_posts_replied_post_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", "ThreadedPost")
+                        .WithOne()
+                        .HasForeignKey("DysonNetwork.Sphere.Post.Post", "ThreadedPostId")
+                        .HasConstraintName("fk_posts_posts_threaded_post_id");
+
+                    b.Navigation("ForwardedPost");
+
+                    b.Navigation("Publisher");
+
+                    b.Navigation("RepliedPost");
+
+                    b.Navigation("ThreadedPost");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PostCollection", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
+                        .WithMany("Collections")
+                        .HasForeignKey("PublisherId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_collections_publishers_publisher_id");
+
+                    b.Navigation("Publisher");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PostReaction", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_reactions_accounts_account_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", "Post")
+                        .WithMany("Reactions")
+                        .HasForeignKey("PostId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_reactions_posts_post_id");
+
+                    b.Navigation("Account");
+
+                    b.Navigation("Post");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .HasConstraintName("fk_publishers_accounts_account_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background")
+                        .WithMany()
+                        .HasForeignKey("BackgroundId")
+                        .HasConstraintName("fk_publishers_files_background_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Picture")
+                        .WithMany()
+                        .HasForeignKey("PictureId")
+                        .HasConstraintName("fk_publishers_files_picture_id");
+
+                    b.Navigation("Account");
+
+                    b.Navigation("Background");
+
+                    b.Navigation("Picture");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.PublisherMember", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_publisher_members_accounts_account_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Publisher", "Publisher")
+                        .WithMany("Members")
+                        .HasForeignKey("PublisherId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_publisher_members_publishers_publisher_id");
+
+                    b.Navigation("Account");
+
+                    b.Navigation("Publisher");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_files_accounts_account_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", null)
+                        .WithMany("Attachments")
+                        .HasForeignKey("PostId")
+                        .HasConstraintName("fk_files_posts_post_id");
+
+                    b.Navigation("Account");
+                });
+
+            modelBuilder.Entity("PostPostCategory", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null)
+                        .WithMany()
+                        .HasForeignKey("CategoriesId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_category_links_post_categories_categories_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", null)
+                        .WithMany()
+                        .HasForeignKey("PostsId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_category_links_posts_posts_id");
+                });
+
+            modelBuilder.Entity("PostPostCollection", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Post.PostCollection", null)
+                        .WithMany()
+                        .HasForeignKey("CollectionsId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_collection_links_post_collections_collections_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", null)
+                        .WithMany()
+                        .HasForeignKey("PostsId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_collection_links_posts_posts_id");
+                });
+
+            modelBuilder.Entity("PostPostTag", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Post.Post", null)
+                        .WithMany()
+                        .HasForeignKey("PostsId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_tag_links_posts_posts_id");
+
+                    b.HasOne("DysonNetwork.Sphere.Post.PostTag", null)
+                        .WithMany()
+                        .HasForeignKey("TagsId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_post_tag_links_post_tags_tags_id");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Account", b =>
+                {
+                    b.Navigation("AuthFactors");
+
+                    b.Navigation("Challenges");
+
+                    b.Navigation("Contacts");
+
+                    b.Navigation("IncomingRelationships");
+
+                    b.Navigation("OutgoingRelationships");
+
+                    b.Navigation("Profile")
+                        .IsRequired();
+
+                    b.Navigation("Sessions");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.Post", b =>
+                {
+                    b.Navigation("Attachments");
+
+                    b.Navigation("Reactions");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Post.Publisher", b =>
+                {
+                    b.Navigation("Collections");
+
+                    b.Navigation("Members");
+
+                    b.Navigation("Posts");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.cs b/DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.cs
new file mode 100644
index 0000000..40db3bb
--- /dev/null
+++ b/DysonNetwork.Sphere/Migrations/20250427154356_AddNotification.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Migrations;
+using NodaTime;
+
+#nullable disable
+
+namespace DysonNetwork.Sphere.Migrations
+{
+    /// <inheritdoc />
+    public partial class AddNotification : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AlterColumn<string>(
+                name: "picture_id",
+                table: "publishers",
+                type: "character varying(128)",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "background_id",
+                table: "publishers",
+                type: "character varying(128)",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "id",
+                table: "files",
+                type: "character varying(128)",
+                maxLength: 128,
+                nullable: false,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AddColumn<Instant>(
+                name: "expired_at",
+                table: "files",
+                type: "timestamp with time zone",
+                nullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "picture_id",
+                table: "account_profiles",
+                type: "character varying(128)",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "background_id",
+                table: "account_profiles",
+                type: "character varying(128)",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.CreateTable(
+                name: "notification_push_subscriptions",
+                columns: table => new
+                {
+                    id = table.Column<Guid>(type: "uuid", nullable: false),
+                    device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
+                    device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
+                    provider = table.Column<int>(type: "integer", nullable: false),
+                    last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
+                    account_id = table.Column<long>(type: "bigint", nullable: false),
+                    created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
+                    updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
+                    deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("pk_notification_push_subscriptions", x => x.id);
+                    table.ForeignKey(
+                        name: "fk_notification_push_subscriptions_accounts_account_id",
+                        column: x => x.account_id,
+                        principalTable: "accounts",
+                        principalColumn: "id",
+                        onDelete: ReferentialAction.Cascade);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "notifications",
+                columns: table => new
+                {
+                    id = table.Column<Guid>(type: "uuid", nullable: false),
+                    title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
+                    subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
+                    content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
+                    meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
+                    priority = table.Column<int>(type: "integer", nullable: false),
+                    viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
+                    account_id = table.Column<long>(type: "bigint", nullable: false),
+                    created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
+                    updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
+                    deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("pk_notifications", x => x.id);
+                    table.ForeignKey(
+                        name: "fk_notifications_accounts_account_id",
+                        column: x => x.account_id,
+                        principalTable: "accounts",
+                        principalColumn: "id",
+                        onDelete: ReferentialAction.Cascade);
+                });
+
+            migrationBuilder.CreateIndex(
+                name: "ix_notification_push_subscriptions_account_id",
+                table: "notification_push_subscriptions",
+                column: "account_id");
+
+            migrationBuilder.CreateIndex(
+                name: "ix_notification_push_subscriptions_device_id",
+                table: "notification_push_subscriptions",
+                column: "device_id",
+                unique: true);
+
+            migrationBuilder.CreateIndex(
+                name: "ix_notification_push_subscriptions_device_token",
+                table: "notification_push_subscriptions",
+                column: "device_token",
+                unique: true);
+
+            migrationBuilder.CreateIndex(
+                name: "ix_notifications_account_id",
+                table: "notifications",
+                column: "account_id");
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropTable(
+                name: "notification_push_subscriptions");
+
+            migrationBuilder.DropTable(
+                name: "notifications");
+
+            migrationBuilder.DropColumn(
+                name: "expired_at",
+                table: "files");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "picture_id",
+                table: "publishers",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "character varying(128)",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "background_id",
+                table: "publishers",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "character varying(128)",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "id",
+                table: "files",
+                type: "text",
+                nullable: false,
+                oldClrType: typeof(string),
+                oldType: "character varying(128)",
+                oldMaxLength: 128);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "picture_id",
+                table: "account_profiles",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "character varying(128)",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "background_id",
+                table: "account_profiles",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "character varying(128)",
+                oldNullable: true);
+        }
+    }
+}
diff --git a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs
index 64639ef..e58ea83 100644
--- a/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs
+++ b/DysonNetwork.Sphere/Migrations/AppDatabaseModelSnapshot.cs
@@ -167,6 +167,125 @@ namespace DysonNetwork.Sphere.Migrations
                     b.ToTable("account_contacts", (string)null);
                 });
 
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasColumnName("id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<string>("Content")
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("content");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<Dictionary<string, object>>("Meta")
+                        .HasColumnType("jsonb")
+                        .HasColumnName("meta");
+
+                    b.Property<int>("Priority")
+                        .HasColumnType("integer")
+                        .HasColumnName("priority");
+
+                    b.Property<string>("Subtitle")
+                        .HasMaxLength(2048)
+                        .HasColumnType("character varying(2048)")
+                        .HasColumnName("subtitle");
+
+                    b.Property<string>("Title")
+                        .HasMaxLength(1024)
+                        .HasColumnType("character varying(1024)")
+                        .HasColumnName("title");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.Property<Instant?>("ViewedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("viewed_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_notifications");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_notifications_account_id");
+
+                    b.ToTable("notifications", (string)null);
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasColumnName("id");
+
+                    b.Property<long>("AccountId")
+                        .HasColumnType("bigint")
+                        .HasColumnName("account_id");
+
+                    b.Property<Instant>("CreatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("created_at");
+
+                    b.Property<Instant?>("DeletedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("deleted_at");
+
+                    b.Property<string>("DeviceId")
+                        .IsRequired()
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("device_id");
+
+                    b.Property<string>("DeviceToken")
+                        .IsRequired()
+                        .HasMaxLength(4096)
+                        .HasColumnType("character varying(4096)")
+                        .HasColumnName("device_token");
+
+                    b.Property<Instant?>("LastUsedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("last_used_at");
+
+                    b.Property<int>("Provider")
+                        .HasColumnType("integer")
+                        .HasColumnName("provider");
+
+                    b.Property<Instant>("UpdatedAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("updated_at");
+
+                    b.HasKey("Id")
+                        .HasName("pk_notification_push_subscriptions");
+
+                    b.HasIndex("AccountId")
+                        .HasDatabaseName("ix_notification_push_subscriptions_account_id");
+
+                    b.HasIndex("DeviceId")
+                        .IsUnique()
+                        .HasDatabaseName("ix_notification_push_subscriptions_device_id");
+
+                    b.HasIndex("DeviceToken")
+                        .IsUnique()
+                        .HasDatabaseName("ix_notification_push_subscriptions_device_token");
+
+                    b.ToTable("notification_push_subscriptions", (string)null);
+                });
+
             modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
                 {
                     b.Property<long>("Id")
@@ -174,7 +293,7 @@ namespace DysonNetwork.Sphere.Migrations
                         .HasColumnName("id");
 
                     b.Property<string>("BackgroundId")
-                        .HasColumnType("text")
+                        .HasColumnType("character varying(128)")
                         .HasColumnName("background_id");
 
                     b.Property<string>("Bio")
@@ -206,7 +325,7 @@ namespace DysonNetwork.Sphere.Migrations
                         .HasColumnName("middle_name");
 
                     b.Property<string>("PictureId")
-                        .HasColumnType("text")
+                        .HasColumnType("character varying(128)")
                         .HasColumnName("picture_id");
 
                     b.Property<Instant>("UpdatedAt")
@@ -692,7 +811,7 @@ namespace DysonNetwork.Sphere.Migrations
                         .HasColumnName("account_id");
 
                     b.Property<string>("BackgroundId")
-                        .HasColumnType("text")
+                        .HasColumnType("character varying(128)")
                         .HasColumnName("background_id");
 
                     b.Property<string>("Bio")
@@ -721,7 +840,7 @@ namespace DysonNetwork.Sphere.Migrations
                         .HasColumnName("nick");
 
                     b.Property<string>("PictureId")
-                        .HasColumnType("text")
+                        .HasColumnType("character varying(128)")
                         .HasColumnName("picture_id");
 
                     b.Property<int>("PublisherType")
@@ -793,7 +912,8 @@ namespace DysonNetwork.Sphere.Migrations
             modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
                 {
                     b.Property<string>("Id")
-                        .HasColumnType("text")
+                        .HasMaxLength(128)
+                        .HasColumnType("character varying(128)")
                         .HasColumnName("id");
 
                     b.Property<long>("AccountId")
@@ -813,6 +933,10 @@ namespace DysonNetwork.Sphere.Migrations
                         .HasColumnType("character varying(4096)")
                         .HasColumnName("description");
 
+                    b.Property<Instant?>("ExpiredAt")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("expired_at");
+
                     b.Property<Dictionary<string, object>>("FileMeta")
                         .HasColumnType("jsonb")
                         .HasColumnName("file_meta");
@@ -955,6 +1079,30 @@ namespace DysonNetwork.Sphere.Migrations
                     b.Navigation("Account");
                 });
 
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.Notification", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_notifications_accounts_account_id");
+
+                    b.Navigation("Account");
+                });
+
+            modelBuilder.Entity("DysonNetwork.Sphere.Account.NotificationPushSubscription", b =>
+                {
+                    b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
+                        .WithMany()
+                        .HasForeignKey("AccountId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired()
+                        .HasConstraintName("fk_notification_push_subscriptions_accounts_account_id");
+
+                    b.Navigation("Account");
+                });
+
             modelBuilder.Entity("DysonNetwork.Sphere.Account.Profile", b =>
                 {
                     b.HasOne("DysonNetwork.Sphere.Storage.CloudFile", "Background")
diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs
index cd12eb1..5f1ac9d 100644
--- a/DysonNetwork.Sphere/Post/PostController.cs
+++ b/DysonNetwork.Sphere/Post/PostController.cs
@@ -27,6 +27,7 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
             .Include(e => e.Attachments)
             .Include(e => e.Categories)
             .Include(e => e.Tags)
+            .Where(e => e.RepliedPostId == null)
             .FilterWithVisibility(currentUser, isListing: true)
             .OrderByDescending(e => e.PublishedAt ?? e.CreatedAt)
             .Skip(offset)
@@ -110,6 +111,8 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
         [MaxLength(32)] public List<string>? Attachments { get; set; }
         public Dictionary<string, object>? Meta { get; set; }
         public Instant? PublishedAt { get; set; }
+        public long? RepliedPostId { get; set; }
+        public long? ForwardedPostId { get; set; }
     }
 
     [HttpPost]
@@ -155,6 +158,22 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
             Publisher = publisher,
         };
 
+        if (request.RepliedPostId is not null)
+        {
+            var repliedPost = await db.Posts.FindAsync(request.RepliedPostId.Value);
+            if (repliedPost is null) return BadRequest("Post replying to was not found.");
+            post.RepliedPost = repliedPost;
+            post.RepliedPostId = repliedPost.Id;
+        }
+
+        if (request.ForwardedPostId is not null)
+        {
+            var forwardedPost = await db.Posts.FindAsync(request.ForwardedPostId.Value);
+            if (forwardedPost is null) return BadRequest("Forwarded post was not found.");
+            post.ForwardedPost = forwardedPost;
+            post.ForwardedPostId = forwardedPost.Id;
+        }
+
         try
         {
             post = await ps.PostAsync(
@@ -226,6 +245,7 @@ public class PostController(AppDatabase db, PostService ps, IEnforcer enforcer)
 
         var post = await db.Posts
             .Where(e => e.Id == id)
+            .Include(e => e.Publisher)
             .Include(e => e.Attachments)
             .FirstOrDefaultAsync();
         if (post is null) return NotFound();
diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs
index 7a5d9cf..51c1a82 100644
--- a/DysonNetwork.Sphere/Program.cs
+++ b/DysonNetwork.Sphere/Program.cs
@@ -130,6 +130,7 @@ builder.Services.AddSwaggerGen(options =>
 builder.Services.AddOpenApi();
 
 builder.Services.AddScoped<AccountService>();
+builder.Services.AddScoped<NotificationService>();
 builder.Services.AddScoped<AuthService>();
 builder.Services.AddScoped<FileService>();
 builder.Services.AddScoped<PublisherService>();
diff --git a/DysonNetwork.Sphere/Storage/CloudFile.cs b/DysonNetwork.Sphere/Storage/CloudFile.cs
index adcefd1..ebe5dfb 100644
--- a/DysonNetwork.Sphere/Storage/CloudFile.cs
+++ b/DysonNetwork.Sphere/Storage/CloudFile.cs
@@ -5,7 +5,7 @@ using NodaTime;
 
 namespace DysonNetwork.Sphere.Storage;
 
-public class RemoteStorageConfig
+public abstract class RemoteStorageConfig
 {
     public string Id { get; set; } = string.Empty;
     public string Label { get; set; } = string.Empty;
@@ -22,7 +22,7 @@ public class RemoteStorageConfig
 
 public class CloudFile : ModelBase
 {
-    public string Id { get; set; } = Guid.NewGuid().ToString();
+    [MaxLength(128)] public string Id { get; set; } = Guid.NewGuid().ToString();
     [MaxLength(1024)] public string Name { get; set; } = string.Empty;
     [MaxLength(4096)] public string? Description { get; set; }
     [Column(TypeName = "jsonb")] public Dictionary<string, object>? FileMeta { get; set; } = null!;
@@ -32,6 +32,7 @@ public class CloudFile : ModelBase
     [MaxLength(256)] public string? Hash { get; set; }
     public long Size { get; set; }
     public Instant? UploadedAt { get; set; }
+    public Instant? ExpiredAt { get; set; }
     [MaxLength(128)] public string? UploadedTo { get; set; }
 
     // Metrics
diff --git a/DysonNetwork.Sphere/Storage/FileService.cs b/DysonNetwork.Sphere/Storage/FileService.cs
index b0a96dd..7096846 100644
--- a/DysonNetwork.Sphere/Storage/FileService.cs
+++ b/DysonNetwork.Sphere/Storage/FileService.cs
@@ -249,9 +249,12 @@ public class CloudFileUnusedRecyclingJob(AppDatabase db, FileService fs, ILogger
         logger.LogInformation("Deleting unused cloud files...");
 
         var cutoff = SystemClock.Instance.GetCurrentInstant() - Duration.FromHours(1);
+        var now = SystemClock.Instance.GetCurrentInstant();
         var files = db.Files
-            .Where(f => f.UsedCount == 0)
-            .Where(f => f.CreatedAt < cutoff)
+            .Where(f =>
+                (f.ExpiredAt == null && f.UsedCount == 0 && f.CreatedAt < cutoff) ||
+                (f.ExpiredAt != null && f.ExpiredAt >= now)
+            )
             .ToList();
 
         logger.LogInformation($"Deleting {files.Count} unused cloud files...");
diff --git a/DysonNetwork.Sphere/appsettings.json b/DysonNetwork.Sphere/appsettings.json
index b4eaf04..c3c393c 100644
--- a/DysonNetwork.Sphere/appsettings.json
+++ b/DysonNetwork.Sphere/appsettings.json
@@ -49,5 +49,17 @@
     "Provider": "recaptcha",
     "ApiKey": "6LfIzSArAAAAAN413MtycDcPlKa636knBSAhbzj-",
     "ApiSecret": ""
+  },
+  "Notifications": {
+    "Push": {
+      "Production": true,
+      "Google": "path/to/config.json",
+      "Apple": {
+        "PrivateKey":  "path/to/cert.p8",
+        "PrivateKeyId": "",
+        "TeamId": "",
+        "BundleIdentifier": ""
+      }
+    }
   }
 }
diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user
index 706b819..8a83c30 100644
--- a/DysonNetwork.sln.DotSettings.user
+++ b/DysonNetwork.sln.DotSettings.user
@@ -1,4 +1,5 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AApnSender_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6aadc2cf048f477d8636fb2def7b73648200_003Fc5_003F2a1973a9_003FApnSender_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticationMiddleware_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe49de78932194d52a02b07486c6d023a24600_003F2f_003F7ab1cc57_003FAuthenticationMiddleware_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizationAppBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ff26593f91746d7a53418a46dc419d1f200_003F4b_003F56550da2_003FAuthorizationAppBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ABucketArgs_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fd515fb889657fcdcace3fed90735057b458ff9e0bb60bded7c8fe8b3a4673c_003FBucketArgs_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>