✨ Action log service grpc
This commit is contained in:
@@ -56,68 +56,6 @@ public class AccountServiceGrpc(
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<Shared.Proto.Account> CreateAccount(CreateAccountRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
// Map protobuf request to domain model
|
|
||||||
var account = new Account
|
|
||||||
{
|
|
||||||
Name = request.Name,
|
|
||||||
Nick = request.Nick,
|
|
||||||
Language = request.Language,
|
|
||||||
IsSuperuser = request.IsSuperuser,
|
|
||||||
ActivatedAt = request.Profile != null ? null : _clock.GetCurrentInstant(),
|
|
||||||
Profile = new AccountProfile
|
|
||||||
{
|
|
||||||
FirstName = request.Profile?.FirstName,
|
|
||||||
LastName = request.Profile?.LastName,
|
|
||||||
// Initialize other profile fields as needed
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add to database
|
|
||||||
_db.Accounts.Add(account);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
_logger.LogInformation("Created new account with ID {AccountId}", account.Id);
|
|
||||||
return account.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Shared.Proto.Account> UpdateAccount(UpdateAccountRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.Id, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var account = await _db.Accounts.FindAsync(accountId);
|
|
||||||
if (account == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found"));
|
|
||||||
|
|
||||||
// Update fields if they are provided in the request
|
|
||||||
if (request.Name != null) account.Name = request.Name;
|
|
||||||
if (request.Nick != null) account.Nick = request.Nick;
|
|
||||||
if (request.Language != null) account.Language = request.Language;
|
|
||||||
if (request.IsSuperuser != null) account.IsSuperuser = request.IsSuperuser.Value;
|
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return account.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Empty> DeleteAccount(DeleteAccountRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.Id, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var account = await _db.Accounts.FindAsync(accountId);
|
|
||||||
if (account == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account {request.Id} not found"));
|
|
||||||
|
|
||||||
_db.Accounts.Remove(account);
|
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return new Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<ListAccountsResponse> ListAccounts(ListAccountsRequest request,
|
public override async Task<ListAccountsResponse> ListAccounts(ListAccountsRequest request,
|
||||||
ServerCallContext context)
|
ServerCallContext context)
|
||||||
{
|
{
|
||||||
@@ -161,210 +99,6 @@ public class AccountServiceGrpc(
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement other service methods following the same pattern...
|
|
||||||
|
|
||||||
// Profile operations
|
|
||||||
public override async Task<Shared.Proto.AccountProfile> GetProfile(GetProfileRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var profile = await _db.AccountProfiles
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(p => p.AccountId == accountId);
|
|
||||||
|
|
||||||
if (profile == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound,
|
|
||||||
$"Profile for account {request.AccountId} not found"));
|
|
||||||
|
|
||||||
return profile.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Shared.Proto.AccountProfile> UpdateProfile(UpdateProfileRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var profile = await _db.AccountProfiles
|
|
||||||
.FirstOrDefaultAsync(p => p.AccountId == accountId);
|
|
||||||
|
|
||||||
if (profile == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound,
|
|
||||||
$"Profile for account {request.AccountId} not found"));
|
|
||||||
|
|
||||||
// Update only the fields specified in the field mask
|
|
||||||
if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("first_name"))
|
|
||||||
profile.FirstName = request.Profile.FirstName;
|
|
||||||
|
|
||||||
if (request.UpdateMask == null || request.UpdateMask.Paths.Contains("last_name"))
|
|
||||||
profile.LastName = request.Profile.LastName;
|
|
||||||
|
|
||||||
// Update other fields similarly...
|
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return profile.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contact operations
|
|
||||||
public override async Task<Shared.Proto.AccountContact> AddContact(AddContactRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var contact = new AccountContact
|
|
||||||
{
|
|
||||||
AccountId = accountId,
|
|
||||||
Type = (AccountContactType)request.Type,
|
|
||||||
Content = request.Content,
|
|
||||||
IsPrimary = request.IsPrimary,
|
|
||||||
VerifiedAt = null
|
|
||||||
};
|
|
||||||
|
|
||||||
_db.AccountContacts.Add(contact);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return contact.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Empty> RemoveContact(RemoveContactRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
if (!Guid.TryParse(request.Id, out var contactId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid contact ID format"));
|
|
||||||
|
|
||||||
var contact = await _db.AccountContacts.FirstOrDefaultAsync(c => c.Id == contactId && c.AccountId == accountId);
|
|
||||||
if (contact == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Contact not found."));
|
|
||||||
|
|
||||||
_db.AccountContacts.Remove(contact);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return new Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<ListContactsResponse> ListContacts(ListContactsRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var query = _db.AccountContacts.AsNoTracking().Where(c => c.AccountId == accountId);
|
|
||||||
|
|
||||||
if (request.VerifiedOnly)
|
|
||||||
query = query.Where(c => c.VerifiedAt != null);
|
|
||||||
|
|
||||||
var contacts = await query.ToListAsync();
|
|
||||||
|
|
||||||
var response = new ListContactsResponse();
|
|
||||||
response.Contacts.AddRange(contacts.Select(c => c.ToProtoValue()));
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Shared.Proto.AccountContact> VerifyContact(VerifyContactRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
// This is a placeholder implementation. In a real-world scenario, you would
|
|
||||||
// have a more robust verification mechanism (e.g., sending a code to the
|
|
||||||
// user's email or phone).
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
if (!Guid.TryParse(request.Id, out var contactId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid contact ID format"));
|
|
||||||
|
|
||||||
var contact = await _db.AccountContacts.FirstOrDefaultAsync(c => c.Id == contactId && c.AccountId == accountId);
|
|
||||||
if (contact == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Contact not found."));
|
|
||||||
|
|
||||||
contact.VerifiedAt = _clock.GetCurrentInstant();
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return contact.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Badge operations
|
|
||||||
public override async Task<Shared.Proto.AccountBadge> AddBadge(AddBadgeRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var badge = new AccountBadge
|
|
||||||
{
|
|
||||||
AccountId = accountId,
|
|
||||||
Type = request.Type,
|
|
||||||
Label = request.Label,
|
|
||||||
Caption = request.Caption,
|
|
||||||
ActivatedAt = _clock.GetCurrentInstant(),
|
|
||||||
ExpiredAt = request.ExpiredAt?.ToInstant(),
|
|
||||||
Meta = request.Meta.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value)
|
|
||||||
};
|
|
||||||
|
|
||||||
_db.Badges.Add(badge);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return badge.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Empty> RemoveBadge(RemoveBadgeRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
if (!Guid.TryParse(request.Id, out var badgeId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid badge ID format"));
|
|
||||||
|
|
||||||
var badge = await _db.Badges.FirstOrDefaultAsync(b => b.Id == badgeId && b.AccountId == accountId);
|
|
||||||
if (badge == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Badge not found."));
|
|
||||||
|
|
||||||
_db.Badges.Remove(badge);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return new Empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<ListBadgesResponse> ListBadges(ListBadgesRequest request, ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var query = _db.Badges.AsNoTracking().Where(b => b.AccountId == accountId);
|
|
||||||
|
|
||||||
if (request.ActiveOnly)
|
|
||||||
query = query.Where(b => b.ExpiredAt == null || b.ExpiredAt > _clock.GetCurrentInstant());
|
|
||||||
|
|
||||||
var badges = await query.ToListAsync();
|
|
||||||
|
|
||||||
var response = new ListBadgesResponse();
|
|
||||||
response.Badges.AddRange(badges.Select(b => b.ToProtoValue()));
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Shared.Proto.AccountProfile> SetActiveBadge(SetActiveBadgeRequest request,
|
|
||||||
ServerCallContext context)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(request.AccountId, out var accountId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid account ID format"));
|
|
||||||
|
|
||||||
var profile = await _db.AccountProfiles.FirstOrDefaultAsync(p => p.AccountId == accountId);
|
|
||||||
if (profile == null)
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, "Profile not found."));
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(request.BadgeId) && !Guid.TryParse(request.BadgeId, out var badgeId))
|
|
||||||
throw new RpcException(new Grpc.Core.Status(StatusCode.InvalidArgument, "Invalid badge ID format"));
|
|
||||||
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return profile.ToProtoValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<ListUserRelationshipSimpleResponse> ListFriends(
|
public override async Task<ListUserRelationshipSimpleResponse> ListFriends(
|
||||||
ListUserRelationshipSimpleRequest request, ServerCallContext context)
|
ListUserRelationshipSimpleRequest request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using DysonNetwork.Shared.Data;
|
using DysonNetwork.Shared.Data;
|
||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using NodaTime.Serialization.Protobuf;
|
||||||
using Point = NetTopologySuite.Geometries.Point;
|
using Point = NetTopologySuite.Geometries.Point;
|
||||||
|
|
||||||
namespace DysonNetwork.Pass.Account;
|
namespace DysonNetwork.Pass.Account;
|
||||||
@@ -56,4 +58,26 @@ public class ActionLog : ModelBase
|
|||||||
public Guid AccountId { get; set; }
|
public Guid AccountId { get; set; }
|
||||||
public Account Account { get; set; } = null!;
|
public Account Account { get; set; } = null!;
|
||||||
public Guid? SessionId { get; set; }
|
public Guid? SessionId { get; set; }
|
||||||
|
|
||||||
|
public Shared.Proto.ActionLog ToProtoValue()
|
||||||
|
{
|
||||||
|
var protoLog = new Shared.Proto.ActionLog
|
||||||
|
{
|
||||||
|
Id = Id.ToString(),
|
||||||
|
Action = Action,
|
||||||
|
UserAgent = UserAgent ?? string.Empty,
|
||||||
|
IpAddress = IpAddress ?? string.Empty,
|
||||||
|
Location = Location?.ToString() ?? string.Empty,
|
||||||
|
AccountId = AccountId.ToString(),
|
||||||
|
CreatedAt = CreatedAt.ToTimestamp()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert Meta dictionary to Struct
|
||||||
|
protoLog.Meta.Add(GrpcTypeHelper.ConvertToValueMap(Meta));
|
||||||
|
|
||||||
|
if (SessionId.HasValue)
|
||||||
|
protoLog.SessionId = SessionId.Value.ToString();
|
||||||
|
|
||||||
|
return protoLog;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -5,7 +5,7 @@ namespace DysonNetwork.Pass.Account;
|
|||||||
|
|
||||||
public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
|
public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
|
||||||
{
|
{
|
||||||
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object> meta)
|
public void CreateActionLog(Guid accountId, string action, Dictionary<string, object?> meta)
|
||||||
{
|
{
|
||||||
var log = new ActionLog
|
var log = new ActionLog
|
||||||
{
|
{
|
||||||
|
114
DysonNetwork.Pass/Account/ActionLogServiceGrpc.cs
Normal file
114
DysonNetwork.Pass/Account/ActionLogServiceGrpc.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using Grpc.Core;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Account;
|
||||||
|
|
||||||
|
public class ActionLogServiceGrpc : Shared.Proto.ActionLogService.ActionLogServiceBase
|
||||||
|
{
|
||||||
|
private readonly ActionLogService _actionLogService;
|
||||||
|
private readonly AppDatabase _db;
|
||||||
|
private readonly ILogger<ActionLogServiceGrpc> _logger;
|
||||||
|
|
||||||
|
public ActionLogServiceGrpc(
|
||||||
|
ActionLogService actionLogService,
|
||||||
|
AppDatabase db,
|
||||||
|
ILogger<ActionLogServiceGrpc> logger)
|
||||||
|
{
|
||||||
|
_actionLogService = actionLogService ?? throw new ArgumentNullException(nameof(actionLogService));
|
||||||
|
_db = db ?? throw new ArgumentNullException(nameof(db));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<CreateActionLogResponse> CreateActionLog(CreateActionLogRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(request.AccountId) || !Guid.TryParse(request.AccountId, out var accountId))
|
||||||
|
{
|
||||||
|
throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.InvalidArgument, "Invalid account ID"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var meta = request.Meta
|
||||||
|
?.Select(x => new KeyValuePair<string, object?>(x.Key, GrpcTypeHelper.ConvertField(x.Value)))
|
||||||
|
.ToDictionary() ?? new Dictionary<string, object?>();
|
||||||
|
|
||||||
|
_actionLogService.CreateActionLog(
|
||||||
|
accountId,
|
||||||
|
request.Action,
|
||||||
|
meta
|
||||||
|
);
|
||||||
|
|
||||||
|
return new CreateActionLogResponse();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error creating action log");
|
||||||
|
throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.Internal, "Failed to create action log"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<ListActionLogsResponse> ListActionLogs(ListActionLogsRequest request,
|
||||||
|
ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(request.AccountId) || !Guid.TryParse(request.AccountId, out var accountId))
|
||||||
|
{
|
||||||
|
throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.InvalidArgument, "Invalid account ID"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = _db.ActionLogs
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(log => log.AccountId == accountId);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.Action))
|
||||||
|
{
|
||||||
|
query = query.Where(log => log.Action == request.Action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply ordering (default to newest first)
|
||||||
|
query = (request.OrderBy?.ToLower() ?? "createdat desc") switch
|
||||||
|
{
|
||||||
|
"createdat" => query.OrderBy(log => log.CreatedAt),
|
||||||
|
"createdat desc" => query.OrderByDescending(log => log.CreatedAt),
|
||||||
|
_ => query.OrderByDescending(log => log.CreatedAt)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply pagination
|
||||||
|
var pageSize = request.PageSize == 0 ? 50 : Math.Min(request.PageSize, 1000);
|
||||||
|
var logs = await query
|
||||||
|
.Take(pageSize + 1) // Fetch one extra to determine if there are more pages
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var hasMore = logs.Count > pageSize;
|
||||||
|
if (hasMore)
|
||||||
|
{
|
||||||
|
logs.RemoveAt(logs.Count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = new ListActionLogsResponse
|
||||||
|
{
|
||||||
|
TotalSize = await query.CountAsync()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasMore)
|
||||||
|
{
|
||||||
|
// In a real implementation, you'd generate a proper page token
|
||||||
|
response.NextPageToken = (logs.LastOrDefault()?.CreatedAt ?? SystemClock.Instance.GetCurrentInstant())
|
||||||
|
.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
response.ActionLogs.AddRange(logs.Select(log => log.ToProtoValue()));
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error listing action logs");
|
||||||
|
throw new RpcException(new Grpc.Core.Status(Grpc.Core.StatusCode.Internal, "Failed to list action logs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -70,6 +70,7 @@ public static class ApplicationConfiguration
|
|||||||
{
|
{
|
||||||
app.MapGrpcService<AccountServiceGrpc>();
|
app.MapGrpcService<AccountServiceGrpc>();
|
||||||
app.MapGrpcService<AuthServiceGrpc>();
|
app.MapGrpcService<AuthServiceGrpc>();
|
||||||
|
app.MapGrpcService<ActionLogServiceGrpc>();
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
@@ -174,6 +174,19 @@ message LevelingInfo {
|
|||||||
repeated int32 experience_per_level = 6;
|
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<string, google.protobuf.Value> 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
|
||||||
|
}
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Service Definitions
|
// Service Definitions
|
||||||
// ====================================
|
// ====================================
|
||||||
@@ -209,10 +222,45 @@ service AccountService {
|
|||||||
rpc ListBlocked(ListUserRelationshipSimpleRequest) returns (ListUserRelationshipSimpleResponse) {}
|
rpc ListBlocked(ListUserRelationshipSimpleRequest) returns (ListUserRelationshipSimpleResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionLogService provides operations for action logs
|
||||||
|
service ActionLogService {
|
||||||
|
rpc CreateActionLog(CreateActionLogRequest) returns (CreateActionLogResponse) {}
|
||||||
|
rpc ListActionLogs(ListActionLogsRequest) returns (ListActionLogsResponse) {}
|
||||||
|
}
|
||||||
|
|
||||||
// ====================================
|
// ====================================
|
||||||
// Request/Response Messages
|
// Request/Response Messages
|
||||||
// ====================================
|
// ====================================
|
||||||
|
|
||||||
|
// ActionLog Requests/Responses
|
||||||
|
message CreateActionLogRequest {
|
||||||
|
string action = 1;
|
||||||
|
map<string, google.protobuf.Value> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListActionLogsRequest {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Account Requests/Responses
|
// Account Requests/Responses
|
||||||
message GetAccountRequest {
|
message GetAccountRequest {
|
||||||
string id = 1; // Account ID to retrieve
|
string id = 1; // Account ID to retrieve
|
||||||
|
@@ -1,15 +1,11 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json;
|
using DysonNetwork.Shared.Proto;
|
||||||
using DysonNetwork.Sphere.Account;
|
|
||||||
using DysonNetwork.Sphere.Pages.Posts;
|
|
||||||
using DysonNetwork.Sphere.Permission;
|
using DysonNetwork.Sphere.Permission;
|
||||||
using DysonNetwork.Sphere.Publisher;
|
using DysonNetwork.Sphere.Publisher;
|
||||||
using DysonNetwork.Sphere.Storage;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NpgsqlTypes;
|
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Post;
|
namespace DysonNetwork.Sphere.Post;
|
||||||
|
|
||||||
@@ -19,8 +15,8 @@ public class PostController(
|
|||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
PostService ps,
|
PostService ps,
|
||||||
PublisherService pub,
|
PublisherService pub,
|
||||||
RelationshipService rels,
|
AccountService.AccountServiceClient accounts,
|
||||||
ActionLogService als
|
ActionLogService.ActionLogServiceClient als
|
||||||
)
|
)
|
||||||
: ControllerBase
|
: ControllerBase
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user