✨ File compression duplicate
This commit is contained in:
parent
0f9e865c0b
commit
b1543f5b08
@ -22,7 +22,6 @@ public class MagicSpellService(AppDatabase db, EmailService email, ILogger<Magic
|
||||
Type = type,
|
||||
ExpiresAt = expiredAt,
|
||||
AffectedAt = affectedAt,
|
||||
Account = account,
|
||||
AccountId = account.Id,
|
||||
Meta = meta
|
||||
};
|
||||
|
@ -42,7 +42,7 @@ public class NotificationService
|
||||
: ApnServerType.Development
|
||||
}, clientFactory.CreateClient());
|
||||
}
|
||||
|
||||
|
||||
// TODO remove all push notification with this device id when this device is logged out
|
||||
|
||||
public async Task<NotificationPushSubscription> SubscribePushNotification(
|
||||
@ -101,16 +101,13 @@ public class NotificationService
|
||||
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
|
||||
if (!isSilent) _ = DeliveryNotification(notification).ConfigureAwait(false);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
@ -42,9 +42,7 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
|
||||
|
||||
var relationship = new Relationship
|
||||
{
|
||||
Account = sender,
|
||||
AccountId = sender.Id,
|
||||
Related = target,
|
||||
RelatedId = target.Id,
|
||||
Status = status
|
||||
};
|
||||
@ -66,9 +64,7 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
|
||||
|
||||
var relationship = new Relationship
|
||||
{
|
||||
Account = sender,
|
||||
AccountId = sender.Id,
|
||||
Related = target,
|
||||
RelatedId = target.Id,
|
||||
Status = RelationshipStatus.Pending,
|
||||
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(7))
|
||||
@ -96,9 +92,7 @@ public class RelationshipService(AppDatabase db, PermissionService pm, IMemoryCa
|
||||
|
||||
var relationshipBackward = new Relationship
|
||||
{
|
||||
Account = relationship.Related,
|
||||
AccountId = relationship.RelatedId,
|
||||
Related = relationship.Account,
|
||||
RelatedId = relationship.AccountId,
|
||||
Status = status
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ public class AppDatabase(
|
||||
public DbSet<Auth.Challenge> AuthChallenges { get; set; }
|
||||
|
||||
public DbSet<Storage.CloudFile> Files { get; set; }
|
||||
|
||||
|
||||
public DbSet<Activity.Activity> Activities { get; set; }
|
||||
|
||||
public DbSet<Post.Publisher> Publishers { get; set; }
|
||||
@ -72,7 +72,8 @@ public class AppDatabase(
|
||||
Nodes =
|
||||
{
|
||||
PermissionService.NewPermissionNode("group:default", "global", "posts.create", true),
|
||||
PermissionService.NewPermissionNode("group:default", "global", "publishers.create", true)
|
||||
PermissionService.NewPermissionNode("group:default", "global", "publishers.create", true),
|
||||
PermissionService.NewPermissionNode("group:default", "global", "files.create", true)
|
||||
}
|
||||
});
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
@ -86,9 +87,9 @@ public class AppDatabase(
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Permission.PermissionGroupMember>()
|
||||
modelBuilder.Entity<PermissionGroupMember>()
|
||||
.HasKey(pg => new { pg.GroupId, pg.Actor });
|
||||
modelBuilder.Entity<Permission.PermissionGroupMember>()
|
||||
modelBuilder.Entity<PermissionGroupMember>()
|
||||
.HasOne(pg => pg.Group)
|
||||
.WithMany(g => g.Members)
|
||||
.HasForeignKey(pg => pg.GroupId)
|
||||
@ -123,6 +124,10 @@ public class AppDatabase(
|
||||
.HasForeignKey(pm => pm.AccountId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Post.Post>()
|
||||
.HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content })
|
||||
.HasIndex(p => p.SearchVector)
|
||||
.HasMethod("GIN");
|
||||
modelBuilder.Entity<Post.Post>()
|
||||
.HasOne(p => p.ThreadedPost)
|
||||
.WithOne()
|
||||
|
@ -50,7 +50,6 @@ public class AuthController(
|
||||
|
||||
var challenge = new Challenge
|
||||
{
|
||||
Account = account,
|
||||
ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddHours(1)),
|
||||
StepTotal = 1,
|
||||
Platform = request.Platform,
|
||||
@ -59,6 +58,7 @@ public class AuthController(
|
||||
IpAddress = ipAddress,
|
||||
UserAgent = userAgent,
|
||||
DeviceId = request.DeviceId,
|
||||
AccountId = account.Id
|
||||
}.Normalize();
|
||||
|
||||
await db.AuthChallenges.AddAsync(challenge);
|
||||
@ -123,13 +123,19 @@ public class AuthController(
|
||||
{
|
||||
challenge.StepRemain--;
|
||||
challenge.BlacklistFactors.Add(factor.Id);
|
||||
db.Update(challenge);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid password.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
challenge.FailedAttempts++;
|
||||
db.Update(challenge);
|
||||
await db.SaveChangesAsync();
|
||||
return BadRequest();
|
||||
return BadRequest("Invalid password.");
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,63 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Sphere.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UnableToNamingThing1 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "has_compression",
|
||||
table: "files",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "activities",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
resource_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
visibility = table.Column<int>(type: "integer", nullable: false),
|
||||
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_activities", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_activities_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_activities_account_id",
|
||||
table: "activities",
|
||||
column: "account_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "activities");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "has_compression",
|
||||
table: "files");
|
||||
}
|
||||
}
|
||||
}
|
@ -9,14 +9,15 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using NpgsqlTypes;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Sphere.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20250501072931_UnableToNamingThing1")]
|
||||
partial class UnableToNamingThing1
|
||||
[Migration("20250501080049_InitialMigration")]
|
||||
partial class InitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@ -790,8 +791,8 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text")
|
||||
b.Property<JsonDocument>("Content")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
@ -840,6 +841,14 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("replied_post_id");
|
||||
|
||||
b.Property<NpgsqlTsVector>("SearchVector")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("tsvector")
|
||||
.HasColumnName("search_vector")
|
||||
.HasAnnotation("Npgsql:TsVectorConfig", "simple")
|
||||
.HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" });
|
||||
|
||||
b.Property<long?>("ThreadedPostId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("threaded_post_id");
|
||||
@ -885,6 +894,11 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
b.HasIndex("RepliedPostId")
|
||||
.HasDatabaseName("ix_posts_replied_post_id");
|
||||
|
||||
b.HasIndex("SearchVector")
|
||||
.HasDatabaseName("ix_posts_search_vector");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN");
|
||||
|
||||
b.HasIndex("ThreadedPostId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_posts_threaded_post_id");
|
@ -4,6 +4,7 @@ using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using NpgsqlTypes;
|
||||
|
||||
#nullable disable
|
||||
|
||||
@ -162,6 +163,30 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "activities",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
resource_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
visibility = table.Column<int>(type: "integer", nullable: false),
|
||||
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_activities", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_activities_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_challenges",
|
||||
columns: table => new
|
||||
@ -395,6 +420,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
uploaded_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
uploaded_to = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||
has_compression = table.Column<bool>(type: "boolean", nullable: false),
|
||||
used_count = table.Column<int>(type: "integer", nullable: false),
|
||||
account_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
post_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
@ -487,7 +513,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
edited_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
published_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
visibility = table.Column<int>(type: "integer", nullable: false),
|
||||
content = table.Column<string>(type: "text", nullable: true),
|
||||
content = table.Column<JsonDocument>(type: "jsonb", nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
views_unique = table.Column<int>(type: "integer", nullable: false),
|
||||
@ -497,6 +523,9 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
threaded_post_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
replied_post_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
forwarded_post_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
search_vector = table.Column<NpgsqlTsVector>(type: "tsvector", nullable: false)
|
||||
.Annotation("Npgsql:TsVectorConfig", "simple")
|
||||
.Annotation("Npgsql:TsVectorProperties", new[] { "title", "description", "content" }),
|
||||
publisher_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),
|
||||
@ -693,6 +722,11 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
column: "name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_activities_account_id",
|
||||
table: "activities",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_challenges_account_id",
|
||||
table: "auth_challenges",
|
||||
@ -806,6 +840,12 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
table: "posts",
|
||||
column: "replied_post_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_posts_search_vector",
|
||||
table: "posts",
|
||||
column: "search_vector")
|
||||
.Annotation("Npgsql:IndexMethod", "GIN");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_posts_threaded_post_id",
|
||||
table: "posts",
|
||||
@ -891,6 +931,9 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "account_relationships");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "activities");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_sessions");
|
||||
|
@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using NpgsqlTypes;
|
||||
|
||||
#nullable disable
|
||||
|
||||
@ -787,8 +788,8 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text")
|
||||
b.Property<JsonDocument>("Content")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
@ -837,6 +838,14 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("replied_post_id");
|
||||
|
||||
b.Property<NpgsqlTsVector>("SearchVector")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("tsvector")
|
||||
.HasColumnName("search_vector")
|
||||
.HasAnnotation("Npgsql:TsVectorConfig", "simple")
|
||||
.HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" });
|
||||
|
||||
b.Property<long?>("ThreadedPostId")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("threaded_post_id");
|
||||
@ -882,6 +891,11 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
b.HasIndex("RepliedPostId")
|
||||
.HasDatabaseName("ix_posts_replied_post_id");
|
||||
|
||||
b.HasIndex("SearchVector")
|
||||
.HasDatabaseName("ix_posts_search_vector");
|
||||
|
||||
NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("SearchVector"), "GIN");
|
||||
|
||||
b.HasIndex("ThreadedPostId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_posts_threaded_post_id");
|
||||
|
@ -1,8 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using NodaTime;
|
||||
using NpgsqlTypes;
|
||||
|
||||
namespace DysonNetwork.Sphere.Post;
|
||||
|
||||
@ -31,8 +33,7 @@ public class Post : ModelBase
|
||||
public Instant? PublishedAt { get; set; }
|
||||
public PostVisibility Visibility { get; set; } = PostVisibility.Public;
|
||||
|
||||
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
||||
public string? Content { get; set; }
|
||||
[Column(TypeName = "jsonb")] public JsonDocument? Content { get; set; }
|
||||
|
||||
public PostType Type { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
@ -49,6 +50,8 @@ public class Post : ModelBase
|
||||
public long? ForwardedPostId { get; set; }
|
||||
public Post? ForwardedPost { get; set; }
|
||||
public ICollection<CloudFile> Attachments { get; set; } = new List<CloudFile>();
|
||||
|
||||
public NpgsqlTsVector SearchVector { get; set; }
|
||||
|
||||
public Publisher Publisher { get; set; } = null!;
|
||||
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
||||
@ -56,7 +59,7 @@ public class Post : ModelBase
|
||||
public ICollection<PostCategory> Categories { get; set; } = new List<PostCategory>();
|
||||
public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
|
||||
|
||||
public bool Empty => Content?.Trim() is { Length: 0 } && Attachments.Count == 0 && ForwardedPostId == null;
|
||||
public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null;
|
||||
}
|
||||
|
||||
public class PostTag : ModelBase
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
using Casbin;
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using DysonNetwork.Sphere.Permission;
|
||||
@ -17,7 +18,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account.Account;
|
||||
var userFriends = await rels.ListAccountFriends(currentUser!);
|
||||
var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser);
|
||||
|
||||
var totalCount = await db.Posts
|
||||
.FilterWithVisibility(currentUser, userFriends, isListing: true)
|
||||
@ -48,7 +49,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account.Account;
|
||||
var userFriends = await rels.ListAccountFriends(currentUser!);
|
||||
var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser);
|
||||
|
||||
var post = await db.Posts
|
||||
.Where(e => e.Id == id)
|
||||
@ -74,7 +75,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
||||
{
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account.Account;
|
||||
var userFriends = await rels.ListAccountFriends(currentUser!);
|
||||
var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser);
|
||||
|
||||
var post = await db.Posts
|
||||
.Where(e => e.Id == id)
|
||||
@ -110,7 +111,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
||||
{
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public JsonDocument? Content { get; set; }
|
||||
public PostVisibility? Visibility { get; set; }
|
||||
public PostType? Type { get; set; }
|
||||
[MaxLength(16)] public List<string>? Tags { get; set; }
|
||||
|
@ -102,9 +102,7 @@ public class PublisherController(AppDatabase db, PublisherService ps, FileServic
|
||||
|
||||
var newMember = new PublisherMember
|
||||
{
|
||||
Account = relatedUser,
|
||||
AccountId = relatedUser.Id,
|
||||
Publisher = publisher,
|
||||
PublisherId = publisher.Id,
|
||||
Role = request.Role,
|
||||
};
|
||||
|
@ -22,13 +22,12 @@ public class PublisherService(AppDatabase db, FileService fs)
|
||||
Bio = bio ?? account.Profile.Bio,
|
||||
Picture = picture ?? account.Profile.Picture,
|
||||
Background = background ?? account.Profile.Background,
|
||||
Account = account,
|
||||
AccountId = account.Id,
|
||||
Members = new List<PublisherMember>
|
||||
{
|
||||
new()
|
||||
{
|
||||
AccountId = account.Id,
|
||||
Account = account,
|
||||
Role = PublisherMemberRole.Owner,
|
||||
JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Storage;
|
||||
|
||||
public abstract class RemoteStorageConfig
|
||||
public class RemoteStorageConfig
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public string Label { get; set; } = string.Empty;
|
||||
@ -41,6 +41,7 @@ public class CloudFile : ModelBase
|
||||
public int UsedCount { get; set; } = 0;
|
||||
|
||||
[JsonIgnore] public Account.Account Account { get; set; } = null!;
|
||||
public long AccountId { get; set; }
|
||||
}
|
||||
|
||||
public enum CloudFileSensitiveMark
|
||||
|
@ -14,7 +14,7 @@ using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag;
|
||||
|
||||
namespace DysonNetwork.Sphere.Storage;
|
||||
|
||||
public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
public class FileService(AppDatabase db, IConfiguration configuration, ILogger<FileService> logger, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
private static readonly string TempFilePrefix = "dyn-cloudfile";
|
||||
|
||||
@ -46,7 +46,7 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
MimeType = contentType,
|
||||
Size = fileSize,
|
||||
Hash = hash,
|
||||
Account = account,
|
||||
AccountId = account.Id
|
||||
};
|
||||
|
||||
switch (contentType.Split('/')[0])
|
||||
@ -83,24 +83,6 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
["ratio"] = aspectRatio,
|
||||
["exif"] = exif
|
||||
};
|
||||
|
||||
var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}");
|
||||
var ogTask = imageSharp.SaveAsWebpAsync(imagePath);
|
||||
modifiedResult.Add((imagePath, string.Empty));
|
||||
|
||||
var compressedClone = imageSharp.Clone();
|
||||
compressedClone.Mutate(i => i.Resize(new ResizeOptions
|
||||
{
|
||||
Mode = ResizeMode.Max,
|
||||
Size = new Size(1024, 1024),
|
||||
})
|
||||
);
|
||||
var imageCompressedPath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed");
|
||||
var compressedTask = compressedClone.SaveAsWebpAsync(imagePath);
|
||||
modifiedResult.Add((imageCompressedPath, ".compressed"));
|
||||
file.HasCompression = true;
|
||||
|
||||
await Task.WhenAll(ogTask, compressedTask);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -131,13 +113,77 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
db.Files.Add(file);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
#pragma warning disable CS4014
|
||||
if (modifiedResult.Count > 0)
|
||||
foreach (var result in modifiedResult)
|
||||
UploadFileToRemoteAsync(file, result.filePath, null, result.suffix, true);
|
||||
else
|
||||
UploadFileToRemoteAsync(file, stream, null);
|
||||
#pragma warning restore CS4014
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
var nfs = scope.ServiceProvider.GetRequiredService<FileService>();
|
||||
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Processed file {fileId}, now trying optimizing if possible...", fileId);
|
||||
|
||||
if (contentType.Split('/')[0] == "image")
|
||||
{
|
||||
file.MimeType = "image/webp";
|
||||
|
||||
List<Task> tasks = [];
|
||||
|
||||
var ogFilePath = Path.Join(configuration.GetValue<string>("Tus:StorePath"), file.Id);
|
||||
using var imageSharp = await Image.LoadAsync<Rgba32>(ogFilePath);
|
||||
var imagePath = Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}");
|
||||
tasks.Add(imageSharp.SaveAsWebpAsync(imagePath));
|
||||
modifiedResult.Add((imagePath, string.Empty));
|
||||
|
||||
if (imageSharp.Size.Width * imageSharp.Size.Height >= 1024 * 1024)
|
||||
{
|
||||
var compressedClone = imageSharp.Clone();
|
||||
compressedClone.Mutate(i => i.Resize(new ResizeOptions
|
||||
{
|
||||
Mode = ResizeMode.Max,
|
||||
Size = new Size(1024, 1024),
|
||||
})
|
||||
);
|
||||
var imageCompressedPath =
|
||||
Path.Join(Path.GetTempPath(), $"{TempFilePrefix}#{file.Id}-compressed");
|
||||
tasks.Add(compressedClone.SaveAsWebpAsync(imageCompressedPath));
|
||||
modifiedResult.Add((imageCompressedPath, ".compressed"));
|
||||
file.HasCompression = true;
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
|
||||
logger.LogInformation("Optimized file {fileId}, now uploading...", fileId);
|
||||
|
||||
if (modifiedResult.Count > 0)
|
||||
{
|
||||
List<Task<CloudFile>> tasks = [];
|
||||
tasks.AddRange(modifiedResult.Select(result =>
|
||||
nfs.UploadFileToRemoteAsync(file, result.filePath, null, result.suffix, true)));
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
file = await tasks.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
file = await nfs.UploadFileToRemoteAsync(file, stream, null);
|
||||
}
|
||||
|
||||
logger.LogInformation("Uploaded file {fileId} done!", fileId);
|
||||
|
||||
var scopedDb = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||
await scopedDb.Files.Where(f => f.Id == file.Id).ExecuteUpdateAsync(setter => setter
|
||||
.SetProperty(f => f.UploadedAt, file.UploadedAt)
|
||||
.SetProperty(f => f.UploadedTo, file.UploadedTo)
|
||||
.SetProperty(f => f.MimeType, file.MimeType)
|
||||
.SetProperty(f => f.HasCompression, file.HasCompression)
|
||||
);
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
logger.LogError(err, "Failed to process {fileId}", fileId);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return file;
|
||||
}
|
||||
@ -179,7 +225,7 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
string? suffix = null, bool selfDestruct = false)
|
||||
{
|
||||
var fileStream = File.OpenRead(filePath);
|
||||
var result = await UploadFileToRemoteAsync(file, fileStream, targetRemote);
|
||||
var result = await UploadFileToRemoteAsync(file, fileStream, targetRemote, suffix);
|
||||
if (selfDestruct) File.Delete(filePath);
|
||||
return result;
|
||||
}
|
||||
@ -210,10 +256,6 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
);
|
||||
|
||||
file.UploadedAt = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
await db.Files.Where(f => f.Id == file.Id).ExecuteUpdateAsync(setter => setter
|
||||
.SetProperty(f => f.UploadedAt, file.UploadedAt)
|
||||
.SetProperty(f => f.UploadedTo, file.UploadedTo)
|
||||
);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user