diff --git a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs index 4299b16..107d051 100644 --- a/DysonNetwork.Pass/Account/AccountServiceGrpc.cs +++ b/DysonNetwork.Pass/Account/AccountServiceGrpc.cs @@ -160,6 +160,26 @@ public class AccountServiceGrpc( return response; } + public override async Task SearchAccount(SearchAccountRequest request, ServerCallContext context) + { + var accounts = await _db.Accounts + .AsNoTracking() + .Where(a => EF.Functions.ILike(a.Name, $"%{request.Query}%")) + .Include(a => a.Profile) + .ToListAsync(); + + var perks = await subscriptions.GetPerkSubscriptionsAsync( + accounts.Select(x => x.Id).ToList() + ); + foreach (var account in accounts) + if (perks.TryGetValue(account.Id, out var perk)) + account.PerkSubscription = perk?.ToReference(); + + var response = new GetAccountBatchResponse(); + response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue())); + return response; + } + public override async Task ListAccounts(ListAccountsRequest request, ServerCallContext context) { diff --git a/DysonNetwork.Shared/Proto/account.proto b/DysonNetwork.Shared/Proto/account.proto index 0306067..26a3102 100644 --- a/DysonNetwork.Shared/Proto/account.proto +++ b/DysonNetwork.Shared/Proto/account.proto @@ -14,232 +14,232 @@ import 'wallet.proto'; // Account represents a user account in the system message Account { - string id = 1; - string name = 2; - string nick = 3; - string language = 4; - string region = 18; - google.protobuf.Timestamp activated_at = 5; - bool is_superuser = 6; + string id = 1; + string name = 2; + string nick = 3; + string language = 4; + string region = 18; + google.protobuf.Timestamp activated_at = 5; + bool is_superuser = 6; - AccountProfile profile = 7; - optional SubscriptionReferenceObject perk_subscription = 16; - repeated AccountContact contacts = 8; - repeated AccountBadge badges = 9; - repeated AccountAuthFactor auth_factors = 10; - repeated AccountConnection connections = 11; - repeated Relationship outgoing_relationships = 12; - repeated Relationship incoming_relationships = 13; + AccountProfile profile = 7; + optional SubscriptionReferenceObject perk_subscription = 16; + repeated AccountContact contacts = 8; + repeated AccountBadge badges = 9; + repeated AccountAuthFactor auth_factors = 10; + repeated AccountConnection connections = 11; + repeated Relationship outgoing_relationships = 12; + repeated Relationship incoming_relationships = 13; - google.protobuf.Timestamp created_at = 14; - google.protobuf.Timestamp updated_at = 15; - - google.protobuf.StringValue automated_id = 17; + google.protobuf.Timestamp created_at = 14; + google.protobuf.Timestamp updated_at = 15; + + google.protobuf.StringValue automated_id = 17; } // Enum for status attitude enum StatusAttitude { - STATUS_ATTITUDE_UNSPECIFIED = 0; - POSITIVE = 1; - NEGATIVE = 2; - NEUTRAL = 3; + STATUS_ATTITUDE_UNSPECIFIED = 0; + POSITIVE = 1; + NEGATIVE = 2; + NEUTRAL = 3; } // AccountStatus represents the status of an account message AccountStatus { - string id = 1; - StatusAttitude attitude = 2; - bool is_online = 3; - bool is_customized = 4; - bool is_invisible = 5; - bool is_not_disturb = 6; - google.protobuf.StringValue label = 7; - google.protobuf.Timestamp cleared_at = 8; - string account_id = 9; - bytes meta = 10; + string id = 1; + StatusAttitude attitude = 2; + bool is_online = 3; + bool is_customized = 4; + bool is_invisible = 5; + bool is_not_disturb = 6; + google.protobuf.StringValue label = 7; + google.protobuf.Timestamp cleared_at = 8; + string account_id = 9; + bytes meta = 10; } // Profile contains detailed information about a user message AccountProfile { - string id = 1; - google.protobuf.StringValue first_name = 2; - google.protobuf.StringValue middle_name = 3; - google.protobuf.StringValue last_name = 4; - google.protobuf.StringValue bio = 5; - google.protobuf.StringValue gender = 6; - google.protobuf.StringValue pronouns = 7; - google.protobuf.StringValue time_zone = 8; - google.protobuf.StringValue location = 9; - google.protobuf.Timestamp birthday = 10; - google.protobuf.Timestamp last_seen_at = 11; + string id = 1; + google.protobuf.StringValue first_name = 2; + google.protobuf.StringValue middle_name = 3; + google.protobuf.StringValue last_name = 4; + google.protobuf.StringValue bio = 5; + google.protobuf.StringValue gender = 6; + google.protobuf.StringValue pronouns = 7; + google.protobuf.StringValue time_zone = 8; + google.protobuf.StringValue location = 9; + google.protobuf.Timestamp birthday = 10; + google.protobuf.Timestamp last_seen_at = 11; - VerificationMark verification = 12; - BadgeReferenceObject active_badge = 13; + VerificationMark verification = 12; + BadgeReferenceObject active_badge = 13; - int32 experience = 14; - int32 level = 15; - double leveling_progress = 16; - double social_credits = 17; - int32 social_credits_level = 18; + int32 experience = 14; + int32 level = 15; + double leveling_progress = 16; + double social_credits = 17; + int32 social_credits_level = 18; - CloudFile picture = 19; - CloudFile background = 20; + CloudFile picture = 19; + CloudFile background = 20; - string account_id = 21; + string account_id = 21; - google.protobuf.Timestamp created_at = 22; - google.protobuf.Timestamp updated_at = 23; + google.protobuf.Timestamp created_at = 22; + google.protobuf.Timestamp updated_at = 23; } // AccountContact represents a contact method for an account message AccountContact { - string id = 1; - AccountContactType type = 2; - google.protobuf.Timestamp verified_at = 3; - bool is_primary = 4; - string content = 5; - string account_id = 6; + string id = 1; + AccountContactType type = 2; + google.protobuf.Timestamp verified_at = 3; + bool is_primary = 4; + string content = 5; + string account_id = 6; - google.protobuf.Timestamp created_at = 7; - google.protobuf.Timestamp updated_at = 8; + google.protobuf.Timestamp created_at = 7; + google.protobuf.Timestamp updated_at = 8; } // Enum for contact types enum AccountContactType { - ACCOUNT_CONTACT_TYPE_UNSPECIFIED = 0; - EMAIL = 1; - PHONE_NUMBER = 2; - ADDRESS = 3; + ACCOUNT_CONTACT_TYPE_UNSPECIFIED = 0; + EMAIL = 1; + PHONE_NUMBER = 2; + ADDRESS = 3; } // AccountAuthFactor represents an authentication factor for an account message AccountAuthFactor { - string id = 1; - AccountAuthFactorType type = 2; - google.protobuf.StringValue secret = 3; // Omitted from JSON serialization in original - map config = 4; // Omitted from JSON serialization in original - int32 trustworthy = 5; - google.protobuf.Timestamp enabled_at = 6; - google.protobuf.Timestamp expired_at = 7; - string account_id = 8; - map created_response = 9; // For initial setup + string id = 1; + AccountAuthFactorType type = 2; + google.protobuf.StringValue secret = 3; // Omitted from JSON serialization in original + map config = 4; // Omitted from JSON serialization in original + int32 trustworthy = 5; + google.protobuf.Timestamp enabled_at = 6; + google.protobuf.Timestamp expired_at = 7; + string account_id = 8; + map created_response = 9; // For initial setup - google.protobuf.Timestamp created_at = 10; - google.protobuf.Timestamp updated_at = 11; + google.protobuf.Timestamp created_at = 10; + google.protobuf.Timestamp updated_at = 11; } // Enum for authentication factor types enum AccountAuthFactorType { - AUTH_FACTOR_TYPE_UNSPECIFIED = 0; - PASSWORD = 1; - EMAIL_CODE = 2; - IN_APP_CODE = 3; - TIMED_CODE = 4; - PIN_CODE = 5; + AUTH_FACTOR_TYPE_UNSPECIFIED = 0; + PASSWORD = 1; + EMAIL_CODE = 2; + IN_APP_CODE = 3; + TIMED_CODE = 4; + PIN_CODE = 5; } // AccountBadge represents a badge associated with an account message AccountBadge { - string id = 1; // Unique identifier for the badge - string type = 2; // Type/category of the badge - google.protobuf.StringValue label = 3; // Display name of the badge - google.protobuf.StringValue caption = 4; // Optional description of the badge - map meta = 5; // Additional metadata for the badge - google.protobuf.Timestamp activated_at = 6; // When the badge was activated - google.protobuf.Timestamp expired_at = 7; // Optional expiration time - string account_id = 8; // ID of the account this badge belongs to + string id = 1; // Unique identifier for the badge + string type = 2; // Type/category of the badge + google.protobuf.StringValue label = 3; // Display name of the badge + google.protobuf.StringValue caption = 4; // Optional description of the badge + map meta = 5; // Additional metadata for the badge + google.protobuf.Timestamp activated_at = 6; // When the badge was activated + google.protobuf.Timestamp expired_at = 7; // Optional expiration time + string account_id = 8; // ID of the account this badge belongs to - google.protobuf.Timestamp created_at = 9; - google.protobuf.Timestamp updated_at = 10; + google.protobuf.Timestamp created_at = 9; + google.protobuf.Timestamp updated_at = 10; } // AccountConnection represents a third-party connection for an account message AccountConnection { - string id = 1; - string provider = 2; - string provided_identifier = 3; - map meta = 4; - google.protobuf.StringValue access_token = 5; // Omitted from JSON serialization - google.protobuf.StringValue refresh_token = 6; // Omitted from JSON serialization - google.protobuf.Timestamp last_used_at = 7; - string account_id = 8; + string id = 1; + string provider = 2; + string provided_identifier = 3; + map meta = 4; + google.protobuf.StringValue access_token = 5; // Omitted from JSON serialization + google.protobuf.StringValue refresh_token = 6; // Omitted from JSON serialization + google.protobuf.Timestamp last_used_at = 7; + string account_id = 8; - google.protobuf.Timestamp created_at = 9; - google.protobuf.Timestamp updated_at = 10; + google.protobuf.Timestamp created_at = 9; + google.protobuf.Timestamp updated_at = 10; } // VerificationMark represents verification status message VerificationMark { - VerificationMarkType type = 1; - string title = 2; - string description = 3; - string verified_by = 4; + VerificationMarkType type = 1; + string title = 2; + string description = 3; + string verified_by = 4; - google.protobuf.Timestamp created_at = 5; - google.protobuf.Timestamp updated_at = 6; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; } enum VerificationMarkType { - VERIFICATION_MARK_TYPE_UNSPECIFIED = 0; - OFFICIAL = 1; - INDIVIDUAL = 2; - ORGANIZATION = 3; - GOVERNMENT = 4; - CREATOR = 5; - DEVELOPER = 6; - PARODY = 7; + VERIFICATION_MARK_TYPE_UNSPECIFIED = 0; + OFFICIAL = 1; + INDIVIDUAL = 2; + ORGANIZATION = 3; + GOVERNMENT = 4; + CREATOR = 5; + DEVELOPER = 6; + PARODY = 7; } // BadgeReferenceObject represents a reference to a badge with minimal information message BadgeReferenceObject { - string id = 1; // Unique identifier for the badge - string type = 2; // Type/category of the badge - google.protobuf.StringValue label = 3; // Display name of the badge - google.protobuf.StringValue caption = 4; // Optional description of the badge - map meta = 5; // Additional metadata for the badge - google.protobuf.Timestamp activated_at = 6; // When the badge was activated - google.protobuf.Timestamp expired_at = 7; // Optional expiration time - string account_id = 8; // ID of the account this badge belongs to + string id = 1; // Unique identifier for the badge + string type = 2; // Type/category of the badge + google.protobuf.StringValue label = 3; // Display name of the badge + google.protobuf.StringValue caption = 4; // Optional description of the badge + map meta = 5; // Additional metadata for the badge + google.protobuf.Timestamp activated_at = 6; // When the badge was activated + google.protobuf.Timestamp expired_at = 7; // Optional expiration time + string account_id = 8; // ID of the account this badge belongs to } // Relationship represents a connection between two accounts message Relationship { - string account_id = 1; - string related_id = 2; - optional Account account = 3; - optional Account related = 4; - int32 status = 5; + string account_id = 1; + string related_id = 2; + optional Account account = 3; + optional Account related = 4; + int32 status = 5; - google.protobuf.Timestamp created_at = 6; - google.protobuf.Timestamp updated_at = 7; + google.protobuf.Timestamp created_at = 6; + google.protobuf.Timestamp updated_at = 7; } // Leveling information message LevelingInfo { - int32 current_level = 1; - int32 current_experience = 2; - int32 next_level_experience = 3; - int32 previous_level_experience = 4; - double level_progress = 5; - repeated int32 experience_per_level = 6; + int32 current_level = 1; + int32 current_experience = 2; + int32 next_level_experience = 3; + int32 previous_level_experience = 4; + double level_progress = 5; + repeated int32 experience_per_level = 6; } // ActionLog represents a record of an action taken by a user message ActionLog { - string id = 1; // Unique identifier for the log entry - string action = 2; // The action that was performed, e.g., "user.login" - map meta = 3; // Metadata associated with the action - google.protobuf.StringValue user_agent = 4; // User agent of the client - google.protobuf.StringValue ip_address = 5; // IP address of the client - google.protobuf.StringValue location = 6; // Geographic location of the client, derived from IP - string account_id = 7; // The account that performed the action - google.protobuf.StringValue session_id = 8; // The session in which the action was performed + string id = 1; // Unique identifier for the log entry + string action = 2; // The action that was performed, e.g., "user.login" + map meta = 3; // Metadata associated with the action + google.protobuf.StringValue user_agent = 4; // User agent of the client + google.protobuf.StringValue ip_address = 5; // IP address of the client + google.protobuf.StringValue location = 6; // Geographic location of the client, derived from IP + string account_id = 7; // The account that performed the action + google.protobuf.StringValue session_id = 8; // The session in which the action was performed - google.protobuf.Timestamp created_at = 9; // When the action log was created + google.protobuf.Timestamp created_at = 9; // When the action log was created } message GetAccountStatusBatchResponse { - repeated AccountStatus statuses = 1; + repeated AccountStatus statuses = 1; } // ==================================== @@ -248,45 +248,46 @@ message GetAccountStatusBatchResponse { // AccountService provides CRUD operations for user accounts and related entities service AccountService { - // Account Operations - rpc GetAccount(GetAccountRequest) returns (Account) {} - rpc GetBotAccount(GetBotAccountRequest) returns (Account) {} - rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {} - rpc GetBotAccountBatch(GetBotAccountBatchRequest) returns (GetAccountBatchResponse) {} - rpc LookupAccountBatch(LookupAccountBatchRequest) returns (GetAccountBatchResponse) {} - rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) {} + // Account Operations + rpc GetAccount(GetAccountRequest) returns (Account) {} + rpc GetBotAccount(GetBotAccountRequest) returns (Account) {} + rpc GetAccountBatch(GetAccountBatchRequest) returns (GetAccountBatchResponse) {} + rpc GetBotAccountBatch(GetBotAccountBatchRequest) returns (GetAccountBatchResponse) {} + rpc LookupAccountBatch(LookupAccountBatchRequest) returns (GetAccountBatchResponse) {} + rpc SearchAccount(SearchAccountRequest) returns (GetAccountBatchResponse) {} + rpc ListAccounts(ListAccountsRequest) returns (ListAccountsResponse) {} - rpc GetAccountStatus(GetAccountRequest) returns (AccountStatus) {} - rpc GetAccountStatusBatch(GetAccountBatchRequest) returns (GetAccountStatusBatchResponse) {} + rpc GetAccountStatus(GetAccountRequest) returns (AccountStatus) {} + rpc GetAccountStatusBatch(GetAccountBatchRequest) returns (GetAccountStatusBatchResponse) {} - // Profile Operations - rpc GetProfile(GetProfileRequest) returns (AccountProfile) {} + // Profile Operations + rpc GetProfile(GetProfileRequest) returns (AccountProfile) {} - // Contact Operations - rpc ListContacts(ListContactsRequest) returns (ListContactsResponse) {} + // Contact Operations + rpc ListContacts(ListContactsRequest) returns (ListContactsResponse) {} - // Badge Operations - rpc ListBadges(ListBadgesRequest) returns (ListBadgesResponse) {} + // Badge Operations + rpc ListBadges(ListBadgesRequest) returns (ListBadgesResponse) {} - // Authentication Factor Operations - rpc ListAuthFactors(ListAuthFactorsRequest) returns (ListAuthFactorsResponse) {} + // Authentication Factor Operations + rpc ListAuthFactors(ListAuthFactorsRequest) returns (ListAuthFactorsResponse) {} - // Connection Operations - rpc ListConnections(ListConnectionsRequest) returns (ListConnectionsResponse) {} + // Connection Operations + rpc ListConnections(ListConnectionsRequest) returns (ListConnectionsResponse) {} - // Relationship Operations - rpc ListRelationships(ListRelationshipsRequest) returns (ListRelationshipsResponse) {} + // Relationship Operations + rpc ListRelationships(ListRelationshipsRequest) returns (ListRelationshipsResponse) {} - rpc GetRelationship(GetRelationshipRequest) returns (GetRelationshipResponse) {} - rpc HasRelationship(GetRelationshipRequest) returns (google.protobuf.BoolValue) {} - rpc ListFriends(ListRelationshipSimpleRequest) returns (ListRelationshipSimpleResponse) {} - rpc ListBlocked(ListRelationshipSimpleRequest) returns (ListRelationshipSimpleResponse) {} + rpc GetRelationship(GetRelationshipRequest) returns (GetRelationshipResponse) {} + rpc HasRelationship(GetRelationshipRequest) returns (google.protobuf.BoolValue) {} + rpc ListFriends(ListRelationshipSimpleRequest) returns (ListRelationshipSimpleResponse) {} + rpc ListBlocked(ListRelationshipSimpleRequest) returns (ListRelationshipSimpleResponse) {} } // ActionLogService provides operations for action logs service ActionLogService { - rpc CreateActionLog(CreateActionLogRequest) returns (CreateActionLogResponse) {} - rpc ListActionLogs(ListActionLogsRequest) returns (ListActionLogsResponse) {} + rpc CreateActionLog(CreateActionLogRequest) returns (CreateActionLogResponse) {} + rpc ListActionLogs(ListActionLogsRequest) returns (ListActionLogsResponse) {} } // ==================================== @@ -295,184 +296,188 @@ service ActionLogService { // ActionLog Requests/Responses message CreateActionLogRequest { - string action = 1; - map meta = 2; - google.protobuf.StringValue user_agent = 3; - google.protobuf.StringValue ip_address = 4; - google.protobuf.StringValue location = 5; - string account_id = 6; - google.protobuf.StringValue session_id = 7; + string action = 1; + map meta = 2; + google.protobuf.StringValue user_agent = 3; + google.protobuf.StringValue ip_address = 4; + google.protobuf.StringValue location = 5; + string account_id = 6; + google.protobuf.StringValue session_id = 7; } message CreateActionLogResponse { - ActionLog action_log = 1; + ActionLog action_log = 1; } message ListActionLogsRequest { - string account_id = 1; - string action = 2; - int32 page_size = 3; - string page_token = 4; - string order_by = 5; + string account_id = 1; + string action = 2; + int32 page_size = 3; + string page_token = 4; + string order_by = 5; } message ListActionLogsResponse { - repeated ActionLog action_logs = 1; - string next_page_token = 2; - int32 total_size = 3; + repeated ActionLog action_logs = 1; + string next_page_token = 2; + int32 total_size = 3; } // Account Requests/Responses message GetAccountRequest { - string id = 1; // Account ID to retrieve + string id = 1; // Account ID to retrieve } message GetBotAccountRequest { - string automated_id = 1; + string automated_id = 1; } message GetAccountBatchRequest { - repeated string id = 1; // Account ID to retrieve + repeated string id = 1; // Account ID to retrieve } message GetBotAccountBatchRequest { - repeated string automated_id = 1; + repeated string automated_id = 1; } message LookupAccountBatchRequest { - repeated string names = 1; + repeated string names = 1; +} + +message SearchAccountRequest { + string query = 1; } message GetAccountBatchResponse { - repeated Account accounts = 1; // List of accounts + repeated Account accounts = 1; // List of accounts } message CreateAccountRequest { - string name = 1; // Required: Unique username - string nick = 2; // Optional: Display name - string language = 3; // Default language - bool is_superuser = 4; // Admin flag - AccountProfile profile = 5; // Initial profile data + string name = 1; // Required: Unique username + string nick = 2; // Optional: Display name + string language = 3; // Default language + bool is_superuser = 4; // Admin flag + AccountProfile profile = 5; // Initial profile data } message UpdateAccountRequest { - string id = 1; // Account ID to update - google.protobuf.StringValue name = 2; // New username if changing - google.protobuf.StringValue nick = 3; // New display name - google.protobuf.StringValue language = 4; // New language - google.protobuf.BoolValue is_superuser = 5; // Admin status + string id = 1; // Account ID to update + google.protobuf.StringValue name = 2; // New username if changing + google.protobuf.StringValue nick = 3; // New display name + google.protobuf.StringValue language = 4; // New language + google.protobuf.BoolValue is_superuser = 5; // Admin status } message DeleteAccountRequest { - string id = 1; // Account ID to delete - bool purge = 2; // If true, permanently delete instead of soft delete + string id = 1; // Account ID to delete + bool purge = 2; // If true, permanently delete instead of soft delete } message ListAccountsRequest { - int32 page_size = 1; // Number of results per page - string page_token = 2; // Token for pagination - string filter = 3; // Filter expression - string order_by = 4; // Sort order + int32 page_size = 1; // Number of results per page + string page_token = 2; // Token for pagination + string filter = 3; // Filter expression + string order_by = 4; // Sort order } message ListAccountsResponse { - repeated Account accounts = 1; // List of accounts - string next_page_token = 2; // Token for next page - int32 total_size = 3; // Total number of accounts + repeated Account accounts = 1; // List of accounts + string next_page_token = 2; // Token for next page + int32 total_size = 3; // Total number of accounts } // Profile Requests/Responses message GetProfileRequest { - string account_id = 1; // Account ID to get profile for + string account_id = 1; // Account ID to get profile for } message UpdateProfileRequest { - string account_id = 1; // Account ID to update profile for - AccountProfile profile = 2; // Profile data to update - google.protobuf.FieldMask update_mask = 3; // Fields to update + string account_id = 1; // Account ID to update profile for + AccountProfile profile = 2; // Profile data to update + google.protobuf.FieldMask update_mask = 3; // Fields to update } // Contact Requests/Responses message AddContactRequest { - string account_id = 1; // Account to add contact to - AccountContactType type = 2; // Type of contact - string content = 3; // Contact content (email, phone, etc.) - bool is_primary = 4; // If this should be the primary contact + string account_id = 1; // Account to add contact to + AccountContactType type = 2; // Type of contact + string content = 3; // Contact content (email, phone, etc.) + bool is_primary = 4; // If this should be the primary contact } message ListContactsRequest { - string account_id = 1; // Account ID to list contacts for - AccountContactType type = 2; // Optional: filter by type - bool verified_only = 3; // Only return verified contacts + string account_id = 1; // Account ID to list contacts for + AccountContactType type = 2; // Optional: filter by type + bool verified_only = 3; // Only return verified contacts } message ListContactsResponse { - repeated AccountContact contacts = 1; // List of contacts + repeated AccountContact contacts = 1; // List of contacts } message VerifyContactRequest { - string id = 1; // Contact ID to verify - string account_id = 2; // Account ID (for validation) - string code = 3; // Verification code + string id = 1; // Contact ID to verify + string account_id = 2; // Account ID (for validation) + string code = 3; // Verification code } // Badge Requests/Responses message ListBadgesRequest { - string account_id = 1; // Account to list badges for - string type = 2; // Optional: filter by type - bool active_only = 3; // Only return active (non-expired) badges + string account_id = 1; // Account to list badges for + string type = 2; // Optional: filter by type + bool active_only = 3; // Only return active (non-expired) badges } message ListBadgesResponse { - repeated AccountBadge badges = 1; // List of badges + repeated AccountBadge badges = 1; // List of badges } message ListAuthFactorsRequest { - string account_id = 1; // Account to list factors for - bool active_only = 2; // Only return active (non-expired) factors + string account_id = 1; // Account to list factors for + bool active_only = 2; // Only return active (non-expired) factors } message ListAuthFactorsResponse { - repeated AccountAuthFactor factors = 1; // List of auth factors + repeated AccountAuthFactor factors = 1; // List of auth factors } message ListConnectionsRequest { - string account_id = 1; // Account to list connections for - string provider = 2; // Optional: filter by provider + string account_id = 1; // Account to list connections for + string provider = 2; // Optional: filter by provider } message ListConnectionsResponse { - repeated AccountConnection connections = 1; // List of connections + repeated AccountConnection connections = 1; // List of connections } // Relationship Requests/Responses message ListRelationshipsRequest { - string account_id = 1; // Account to list relationships for - optional int32 status = 2; // Filter by status - int32 page_size = 5; // Number of results per page - string page_token = 6; // Token for pagination + string account_id = 1; // Account to list relationships for + optional int32 status = 2; // Filter by status + int32 page_size = 5; // Number of results per page + string page_token = 6; // Token for pagination } message ListRelationshipsResponse { - repeated Relationship relationships = 1; // List of relationships - string next_page_token = 2; // Token for next page - int32 total_size = 3; // Total number of relationships + repeated Relationship relationships = 1; // List of relationships + string next_page_token = 2; // Token for next page + int32 total_size = 3; // Total number of relationships } message GetRelationshipRequest { - string account_id = 1; - string related_id = 2; - optional int32 status = 3; + string account_id = 1; + string related_id = 2; + optional int32 status = 3; } message GetRelationshipResponse { - optional Relationship relationship = 1; + optional Relationship relationship = 1; } message ListRelationshipSimpleRequest { - string account_id = 1; + string account_id = 1; } message ListRelationshipSimpleResponse { - repeated string accounts_id = 1; + repeated string accounts_id = 1; } diff --git a/DysonNetwork.Shared/Registry/AccountClientHelper.cs b/DysonNetwork.Shared/Registry/AccountClientHelper.cs index 9a3bf8a..7c3899b 100644 --- a/DysonNetwork.Shared/Registry/AccountClientHelper.cs +++ b/DysonNetwork.Shared/Registry/AccountClientHelper.cs @@ -11,7 +11,7 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts) var response = await accounts.GetAccountAsync(request); return response; } - + public async Task GetBotAccount(Guid automatedId) { var request = new GetBotAccountRequest { AutomatedId = automatedId.ToString() }; @@ -26,7 +26,14 @@ public class AccountClientHelper(AccountService.AccountServiceClient accounts) var response = await accounts.GetAccountBatchAsync(request); return response.Accounts.ToList(); } - + + public async Task> SearchAccounts(string query) + { + var request = new SearchAccountRequest { Query = query }; + var response = await accounts.SearchAccountAsync(request); + return response.Accounts.ToList(); + } + public async Task> GetBotAccountBatch(List automatedIds) { var request = new GetBotAccountBatchRequest(); diff --git a/DysonNetwork.Sphere/Autocompletion/AutocompletionService.cs b/DysonNetwork.Sphere/Autocompletion/AutocompletionService.cs index 10221de..a7c6118 100644 --- a/DysonNetwork.Sphere/Autocompletion/AutocompletionService.cs +++ b/DysonNetwork.Sphere/Autocompletion/AutocompletionService.cs @@ -1,10 +1,12 @@ +using DysonNetwork.Shared.Models; +using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Autocompletion; -public class AutocompletionService(AppDatabase db) +public class AutocompletionService(AppDatabase db, AccountClientHelper accountsHelper) { - public async Task> GetAutocompletion(string content, int limit = 10) + public async Task> GetAutocompletion(string content, Guid? chatId = null, Guid? realmId = null, int limit = 10) { if (string.IsNullOrWhiteSpace(content)) return []; @@ -14,7 +16,8 @@ public class AutocompletionService(AppDatabase db) var afterAt = content[1..]; string type; string query; - if (afterAt.Contains('/')) + bool hadSlash = afterAt.Contains('/'); + if (hadSlash) { var parts = afterAt.Split('/', 2); type = parts[0]; @@ -25,7 +28,8 @@ public class AutocompletionService(AppDatabase db) type = "u"; query = afterAt; } - return await AutocompleteAt(type, query, limit); + + return await AutocompleteAt(type, query, chatId, realmId, hadSlash, limit); } if (!content.StartsWith(':')) return []; @@ -33,15 +37,49 @@ public class AutocompletionService(AppDatabase db) var query = content[1..]; return await AutocompleteSticker(query, limit); } - } - private async Task> AutocompleteAt(string type, string query, int limit) + private async Task> AutocompleteAt(string type, string query, Guid? chatId, Guid? realmId, bool hadSlash, + int limit) { var results = new List(); switch (type) { + case "u": + var allAccounts = await accountsHelper.SearchAccounts(query); + var filteredAccounts = allAccounts; + + if (chatId.HasValue) + { + var chatMemberIds = await db.ChatMembers + .Where(m => m.ChatRoomId == chatId.Value && m.JoinedAt != null && m.LeaveAt == null) + .Select(m => m.AccountId) + .ToListAsync(); + var chatMemberIdStrings = chatMemberIds.Select(id => id.ToString()).ToHashSet(); + filteredAccounts = allAccounts.Where(a => chatMemberIdStrings.Contains(a.Id)).ToList(); + } + else if (realmId.HasValue) + { + var realmMemberIds = await db.RealmMembers + .Where(m => m.RealmId == realmId.Value && m.LeaveAt == null) + .Select(m => m.AccountId) + .ToListAsync(); + var realmMemberIdStrings = realmMemberIds.Select(id => id.ToString()).ToHashSet(); + filteredAccounts = allAccounts.Where(a => realmMemberIdStrings.Contains(a.Id)).ToList(); + } + + var users = filteredAccounts + .Take(limit) + .Select(a => new DysonNetwork.Shared.Models.Autocompletion + { + Type = "user", + Keyword = "@" + (hadSlash ? "u/" : "") + a.Name, + Data = SnAccount.FromProtoValue(a) + }) + .ToList(); + results.AddRange(users); + break; case "p": var publishers = await db.Publishers .Where(p => EF.Functions.Like(p.Name, $"{query}%") || EF.Functions.Like(p.Nick, $"{query}%")) @@ -49,7 +87,7 @@ public class AutocompletionService(AppDatabase db) .Select(p => new DysonNetwork.Shared.Models.Autocompletion { Type = "publisher", - Keyword = p.Name, + Keyword = "@p/" + p.Name, Data = p }) .ToListAsync(); @@ -63,7 +101,7 @@ public class AutocompletionService(AppDatabase db) .Select(r => new DysonNetwork.Shared.Models.Autocompletion { Type = "realm", - Keyword = r.Slug, + Keyword = "@r/" + r.Slug, Data = r }) .ToListAsync(); @@ -77,7 +115,7 @@ public class AutocompletionService(AppDatabase db) .Select(c => new DysonNetwork.Shared.Models.Autocompletion { Type = "chat", - Keyword = c.Name!, + Keyword = "@c/" + c.Name, Data = c }) .ToListAsync(); diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs index 9c7d4f0..22159fc 100644 --- a/DysonNetwork.Sphere/Chat/ChatController.cs +++ b/DysonNetwork.Sphere/Chat/ChatController.cs @@ -332,7 +332,7 @@ public partial class ChatController( return Ok(response); } - [HttpGet("{roomId:guid}/autocomplete")] + [HttpPost("{roomId:guid}/autocomplete")] public async Task>> ChatAutoComplete([FromBody] AutocompletionRequest request, Guid roomId) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) @@ -344,7 +344,7 @@ public partial class ChatController( if (!isMember) return StatusCode(403, "You are not a member of this chat room."); - var result = await aus.GetAutocompletion(request.Content, limit: 10); + var result = await aus.GetAutocompletion(request.Content, chatId: roomId, limit: 10); return Ok(result); } } diff --git a/DysonNetwork.Sphere/Program.cs b/DysonNetwork.Sphere/Program.cs index 5ae7279..f04f284 100644 --- a/DysonNetwork.Sphere/Program.cs +++ b/DysonNetwork.Sphere/Program.cs @@ -14,7 +14,7 @@ builder.ConfigureAppKestrel(builder.Configuration); // Add application services -builder.Services.AddAppServices(builder.Configuration); +builder.Services.AddAppServices(); builder.Services.AddAppRateLimiting(); builder.Services.AddAppAuthentication(); builder.Services.AddDysonAuth(); diff --git a/DysonNetwork.Sphere/Publisher/PublisherService.cs b/DysonNetwork.Sphere/Publisher/PublisherService.cs index 092889b..8152ba3 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherService.cs @@ -282,9 +282,9 @@ public class PublisherService( public int SubscribersCount { get; set; } } - private const string PublisherStatsCacheKey = "PublisherStats_{0}"; - private const string PublisherHeatmapCacheKey = "PublisherHeatmap_{0}"; - private const string PublisherFeatureCacheKey = "PublisherFeature_{0}_{1}"; + private const string PublisherStatsCacheKey = "publisher:{0}:stats"; + private const string PublisherHeatmapCacheKey = "publisher:{0}:heatmap"; + private const string PublisherFeatureCacheKey = "publisher:{0}:feature:{1}"; public async Task GetPublisherStats(string name) { @@ -329,7 +329,7 @@ public class PublisherService( public async Task GetPublisherHeatmap(string name) { var cacheKey = string.Format(PublisherHeatmapCacheKey, name); - var heatmap = await cache.GetAsync(cacheKey); + var heatmap = await cache.GetAsync(cacheKey); if (heatmap is not null) return heatmap; diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 24dcf82..ceeeaf8 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -15,6 +15,7 @@ using System.Text.Json.Serialization; using System.Threading.RateLimiting; using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.GeoIp; +using DysonNetwork.Shared.Registry; using DysonNetwork.Sphere.Autocompletion; using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Discovery; @@ -25,7 +26,7 @@ namespace DysonNetwork.Sphere.Startup; public static class ServiceCollectionExtensions { - public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddAppServices(this IServiceCollection services) { services.AddLocalization(options => options.ResourcesPath = "Resources"); @@ -119,6 +120,7 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); var translationProvider = configuration["Translation:Provider"]?.ToLower();