Release IKAR 1.0.7: port navigation and sharing improvements, harden calls
Tests / test (push) Canceled after 0s
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s

This commit is contained in:
sevenhill
2026-10-08 20:38:54 +03:00
parent 68f7957043
commit 317cfdae38
37 changed files with 1483 additions and 282 deletions
@@ -0,0 +1,40 @@
using System.Text.Json;
using Ikar.Server.Data.Entities;
using Ikar.Server.Controllers;
using Ikar.Server.Infrastructure;
using Ikar.Shared;
using Microsoft.AspNetCore.Mvc;
namespace Ikar.Server.Tests;
public sealed partial class QlyraCompatibilityControllerTests
{
[Fact]
public async Task MessageLookup_RespectsMembershipChatAndHiddenMessages()
{
await using var database = new SqliteTestDatabase();
await using var db = await database.CreateContextAsync();
var owner = new User { Username = "lookup", NormalizedUsername = "LOOKUP", DisplayName = "Lookup", PasswordHash = "test" };
var outsider = new User { Username = "outsider", NormalizedUsername = "OUTSIDER", DisplayName = "Other", PasswordHash = "test" };
var chat = new Chat { Type = ChatType.Group, Title = "Lookup", CreatedBy = owner };
chat.Members.Add(new ChatMember { Chat = chat, User = owner, IsOwner = true, Role = ChatMemberRole.Owner });
var message = new Message { Chat = chat, Sender = owner, Text = "Old reply target" };
chat.Messages.Add(message);
db.Users.Add(outsider);
db.Chats.Add(chat);
await db.SaveChangesAsync();
var mapper = new QlyraClientIdMapper(db);
var chatId = await mapper.GetOrCreateAsync("chat", chat.Id, CancellationToken.None);
var messageId = await mapper.GetOrCreateAsync("message", message.Id, CancellationToken.None);
using var document = JsonDocument.Parse($"{{\"chatId\":{chatId},\"messageId\":{messageId}}}");
var request = new QlyraCompatibilityRequest(document.RootElement.Clone());
var controller = CreateController(db, owner.Id);
var action = Assert.IsType<OkObjectResult>(await controller.Dispatch(71, request, CancellationToken.None));
var payload = JsonSerializer.SerializeToElement(Assert.IsType<QlyraCompatibilityResponse>(action.Value).Payload);
Assert.Equal("Old reply target", payload.GetProperty("message").GetProperty("text").GetString());
Assert.IsType<NotFoundObjectResult>(await CreateController(db, outsider.Id).Dispatch(71, request, CancellationToken.None));
db.MessageHiddenForUsers.Add(new MessageHiddenForUser { UserId = owner.Id, MessageId = message.Id });
await db.SaveChangesAsync();
Assert.IsType<NotFoundObjectResult>(await controller.Dispatch(71, request, CancellationToken.None));
}
}
@@ -28,7 +28,7 @@ public sealed partial class QlyraCompatibilityControllerTests
int[] opcodes =
[
1, 8, 16, 17, 18, 19, 20, 21, 22, 23, 26, 27, 28, 29, 32, 34, 35, 36, 39, 41, 43, 46,
48, 49, 50, 51, 52, 53, 54, 55, 57, 58, 59, 60, 64, 65, 66, 67, 68, 73, 75, 76, 77, 78,
48, 49, 50, 51, 52, 53, 54, 55, 57, 58, 59, 60, 64, 65, 66, 67, 68, 71, 73, 75, 76, 77, 78,
79, 80, 82, 83, 84, 87, 88, 89, 91, 96, 97, 104, 107, 108, 109, 110, 111, 112,
113, 115, 118, 145, 161, 162, 164, 166, 178, 179, 181, 198, 202, 208, 210, 214, 215,
259, 272, 273, 274, 275, 276, 290, 302, 304, 306
@@ -74,6 +74,7 @@ VideoPreviewService? videoPreviewService = null) : ControllerBase
43 => await RemoveProfilePhotoAsync(opcode, cancellationToken),
48 => await GetChatInfoAsync(opcode, request.Payload, cancellationToken),
49 => await GetHistoryAsync(opcode, request.Payload, cancellationToken),
71 => await GetHistoryAsync(opcode, request.Payload, cancellationToken),
50 => await MarkReadAsync(opcode, request.Payload, cancellationToken),
51 => await GetChatMediaAsync(opcode, request.Payload, cancellationToken),
52 => await DeleteChatAsync(opcode, request.Payload, cancellationToken),
@@ -1738,6 +1739,13 @@ VideoPreviewService? videoPreviewService = null) : ControllerBase
var visibleMessages = chat.Messages
.Where(message => message.DeletedAt is null && !hiddenMessageIds.Contains(message.Id))
.Where(message => delayed ? message.ScheduledForUnixMs != null && message.SenderId == user.Id : message.ScheduledForUnixMs == null);
if (opcode == 71)
{
var selected = await ResolveMessageAsync(chat.Id, ReadInt(payload, "messageId"), cancellationToken);
if (selected is null || !visibleMessages.Any(message => message.Id == selected.Id))
return NotFound(QlyraCompatibilityResponse.Error(opcode, "MESSAGE_NOT_FOUND", "Сообщение не найдено."));
return Ok(QlyraCompatibilityResponse.Ok(opcode, new { message = await BuildMessageAsync(selected, cancellationToken) }));
}
if (delayed) anchor = DateTimeOffset.MaxValue;
if (ReadInt(payload, "postId") is int clientPostId)
{