Implement basic UserService functions
This commit is contained in:
parent
bb1f4e6e27
commit
eace0cb0de
9 changed files with 89 additions and 13 deletions
|
@ -63,11 +63,11 @@ public class ASActor : ASObject {
|
||||||
|
|
||||||
[J("https://www.w3.org/ns/activitystreams#followers")]
|
[J("https://www.w3.org/ns/activitystreams#followers")]
|
||||||
[JC(typeof(ASCollectionConverter))]
|
[JC(typeof(ASCollectionConverter))]
|
||||||
public ASCollection<ASActor>? Followers { get; set; }
|
public ASCollection? Followers { get; set; } //FIXME: <ASActor>
|
||||||
|
|
||||||
[J("https://www.w3.org/ns/activitystreams#following")]
|
[J("https://www.w3.org/ns/activitystreams#following")]
|
||||||
[JC(typeof(ASCollectionConverter))]
|
[JC(typeof(ASCollectionConverter))]
|
||||||
public ASCollection<ASActor>? Following { get; set; }
|
public ASCollection? Following { get; set; } //FIXME: <ASActor>
|
||||||
|
|
||||||
[J("https://www.w3.org/ns/activitystreams#sharedInbox")]
|
[J("https://www.w3.org/ns/activitystreams#sharedInbox")]
|
||||||
[JC(typeof(ASLinkConverter))]
|
[JC(typeof(ASLinkConverter))]
|
||||||
|
|
|
@ -11,10 +11,6 @@ namespace Iceshrimp.Backend.Core.Federation;
|
||||||
public class Fetch {
|
public class Fetch {
|
||||||
private const string Accept = "application/activity+json";
|
private const string Accept = "application/activity+json";
|
||||||
|
|
||||||
//TODO: required attribute doesn't work with Newtonsoft.Json it appears
|
|
||||||
//TODO: enforce @type values
|
|
||||||
//TODO: firstordefault -> singleordefault
|
|
||||||
|
|
||||||
public static void Test2() {
|
public static void Test2() {
|
||||||
var thing = FetchActivity("https://staging.e2net.social/users/9esresfwle/outbox?page=true");
|
var thing = FetchActivity("https://staging.e2net.social/users/9esresfwle/outbox?page=true");
|
||||||
var collection = thing.ToObject<List<ASCollection>>();
|
var collection = thing.ToObject<List<ASCollection>>();
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
using Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
||||||
|
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||||
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Iceshrimp.Backend.Core.Federation.Services;
|
||||||
|
|
||||||
|
//TODO: required attribute doesn't work with Newtonsoft.Json it appears
|
||||||
|
//TODO: enforce @type values
|
||||||
|
|
||||||
|
public class ActivityPubService(HttpClient client, HttpRequestService httpRqSvc) {
|
||||||
|
private async Task<JArray> FetchActivity(string url) {
|
||||||
|
var request = httpRqSvc.Get(url, ["application/activity+json"]);
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
var input = await response.Content.ReadAsStringAsync();
|
||||||
|
var json = JsonConvert.DeserializeObject<JObject?>(input);
|
||||||
|
|
||||||
|
var res = LDHelpers.Expand(json);
|
||||||
|
if (res == null) throw new Exception("Failed to expand JSON-LD object");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ASActor> FetchActor(string uri) {
|
||||||
|
var activity = await FetchActivity(uri);
|
||||||
|
var actor = activity.ToObject<List<ASActor>>();
|
||||||
|
return actor?.First() ?? throw new Exception("Failed to fetch actor");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
using Iceshrimp.Backend.Core.Federation.WebFinger;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Federation.WebFinger;
|
namespace Iceshrimp.Backend.Core.Federation.Services;
|
||||||
|
|
||||||
public class UserResolver(ILogger<UserResolver> logger, UserService userSvc, DatabaseContext db) {
|
public class UserResolver(ILogger<UserResolver> logger, UserService userSvc, DatabaseContext db) {
|
||||||
private static string AcctToDomain(string acct) =>
|
private static string AcctToDomain(string acct) =>
|
||||||
|
@ -24,7 +25,7 @@ public class UserResolver(ILogger<UserResolver> logger, UserService userSvc, Dat
|
||||||
private async Task<(string Acct, string Uri)> WebFinger(string query) {
|
private async Task<(string Acct, string Uri)> WebFinger(string query) {
|
||||||
logger.LogDebug("Running WebFinger for query '{query}'", query);
|
logger.LogDebug("Running WebFinger for query '{query}'", query);
|
||||||
|
|
||||||
var finger = new WebFinger(query);
|
var finger = new WebFinger.WebFinger(query);
|
||||||
var responses = new Dictionary<string, WebFingerResponse>();
|
var responses = new Dictionary<string, WebFingerResponse>();
|
||||||
var fingerRes = await finger.Resolve();
|
var fingerRes = await finger.Resolve();
|
||||||
if (fingerRes == null) throw new Exception($"Failed to WebFinger '{query}'");
|
if (fingerRes == null) throw new Exception($"Failed to WebFinger '{query}'");
|
||||||
|
@ -38,7 +39,7 @@ public class UserResolver(ILogger<UserResolver> logger, UserService userSvc, Dat
|
||||||
if (fingerRes == null) {
|
if (fingerRes == null) {
|
||||||
logger.LogDebug("AP uri didn't match query, re-running WebFinger for '{apUri}'", apUri);
|
logger.LogDebug("AP uri didn't match query, re-running WebFinger for '{apUri}'", apUri);
|
||||||
|
|
||||||
finger = new WebFinger(apUri);
|
finger = new WebFinger.WebFinger(apUri);
|
||||||
fingerRes = await finger.Resolve();
|
fingerRes = await finger.Resolve();
|
||||||
|
|
||||||
if (fingerRes == null) throw new Exception($"Failed to WebFinger '{apUri}'");
|
if (fingerRes == null) throw new Exception($"Failed to WebFinger '{apUri}'");
|
||||||
|
@ -52,7 +53,7 @@ public class UserResolver(ILogger<UserResolver> logger, UserService userSvc, Dat
|
||||||
if (fingerRes == null) {
|
if (fingerRes == null) {
|
||||||
logger.LogDebug("Acct uri didn't match query, re-running WebFinger for '{acctUri}'", acctUri);
|
logger.LogDebug("Acct uri didn't match query, re-running WebFinger for '{acctUri}'", acctUri);
|
||||||
|
|
||||||
finger = new WebFinger(acctUri);
|
finger = new WebFinger.WebFinger(acctUri);
|
||||||
fingerRes = await finger.Resolve();
|
fingerRes = await finger.Resolve();
|
||||||
|
|
||||||
if (fingerRes == null) throw new Exception($"Failed to WebFinger '{acctUri}'");
|
if (fingerRes == null) throw new Exception($"Failed to WebFinger '{acctUri}'");
|
||||||
|
@ -84,6 +85,6 @@ public class UserResolver(ILogger<UserResolver> logger, UserService userSvc, Dat
|
||||||
if (user != null) return user;
|
if (user != null) return user;
|
||||||
|
|
||||||
// Pass the job on to userSvc, which will create the user
|
// Pass the job on to userSvc, which will create the user
|
||||||
return await userSvc.CreateUser(acct, uri);
|
return await userSvc.CreateUser(uri, acct);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||||
using JR = System.Text.Json.Serialization.JsonRequiredAttribute;
|
using JR = System.Text.Json.Serialization.JsonRequiredAttribute;
|
||||||
|
// ReSharper disable ClassNeverInstantiated.Global
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Federation.WebFinger;
|
namespace Iceshrimp.Backend.Core.Federation.WebFinger;
|
||||||
|
|
||||||
|
@ -9,6 +11,7 @@ public sealed class Link {
|
||||||
[J("href")] public string? Href { get; set; }
|
[J("href")] public string? Href { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
|
||||||
public sealed class WebFingerResponse {
|
public sealed class WebFingerResponse {
|
||||||
[J("links"), JR] public List<Link> Links { get; set; } = null!;
|
[J("links"), JR] public List<Link> Links { get; set; } = null!;
|
||||||
[J("subject"), JR] public string Subject { get; set; } = null!;
|
[J("subject"), JR] public string Subject { get; set; } = null!;
|
||||||
|
|
|
@ -3,9 +3,10 @@ using System.Net.Http.Headers;
|
||||||
namespace Iceshrimp.Backend.Core.Helpers;
|
namespace Iceshrimp.Backend.Core.Helpers;
|
||||||
|
|
||||||
public static class HttpClientHelpers {
|
public static class HttpClientHelpers {
|
||||||
|
//TODO: replace with HttpClient service
|
||||||
public static readonly HttpClient HttpClient = new() {
|
public static readonly HttpClient HttpClient = new() {
|
||||||
DefaultRequestHeaders = {
|
DefaultRequestHeaders = {
|
||||||
UserAgent = { ProductInfoHeaderValue.Parse("Iceshrimp.NET/0.0.1") }
|
UserAgent = { ProductInfoHeaderValue.Parse("Iceshrimp.NET/0.0.1") }
|
||||||
} //FIXME (instance domain comment in parentheses doesn't work?)
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
using Iceshrimp.Backend.Core.Configuration;
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
|
using Iceshrimp.Backend.Core.Federation;
|
||||||
|
using Iceshrimp.Backend.Core.Federation.Services;
|
||||||
using Iceshrimp.Backend.Core.Federation.WebFinger;
|
using Iceshrimp.Backend.Core.Federation.WebFinger;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
|
||||||
|
@ -6,9 +8,18 @@ namespace Iceshrimp.Backend.Core.Helpers;
|
||||||
|
|
||||||
public static class ServiceExtensions {
|
public static class ServiceExtensions {
|
||||||
public static void AddServices(this IServiceCollection services) {
|
public static void AddServices(this IServiceCollection services) {
|
||||||
|
// Transient = instantiated per request and class
|
||||||
|
//services.AddTransient<T>();
|
||||||
|
|
||||||
|
// Scoped = instantiated per request
|
||||||
services.AddScoped<UserResolver>();
|
services.AddScoped<UserResolver>();
|
||||||
services.AddScoped<UserService>();
|
services.AddScoped<UserService>();
|
||||||
services.AddScoped<NoteService>();
|
services.AddScoped<NoteService>();
|
||||||
|
|
||||||
|
// Singleton = instantiated once across application lifetime
|
||||||
|
services.AddSingleton<HttpClient>();
|
||||||
|
services.AddSingleton<HttpRequestService>();
|
||||||
|
services.AddSingleton<ActivityPubService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration) {
|
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration) {
|
||||||
|
|
29
Iceshrimp.Backend/Core/Services/HttpRequestService.cs
Normal file
29
Iceshrimp.Backend/Core/Services/HttpRequestService.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Iceshrimp.Backend.Core.Services;
|
||||||
|
|
||||||
|
public class HttpRequestService(IOptions<Config.StaticSection> options) {
|
||||||
|
private HttpRequestMessage GenerateRequest(string url, IEnumerable<string>? accept, HttpMethod method) {
|
||||||
|
var message = new HttpRequestMessage {
|
||||||
|
RequestUri = new Uri(url),
|
||||||
|
Method = method,
|
||||||
|
//Headers = { UserAgent = { ProductInfoHeaderValue.Parse(options.Value.UserAgent) } }
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO: fix the user-agent so the commented out bit above works
|
||||||
|
message.Headers.TryAddWithoutValidation("User-Agent", options.Value.UserAgent);
|
||||||
|
|
||||||
|
if (accept != null) {
|
||||||
|
foreach (var type in accept.Select(MediaTypeWithQualityHeaderValue.Parse))
|
||||||
|
message.Headers.Accept.Add(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpRequestMessage Get(string url, IEnumerable<string>? accept) {
|
||||||
|
return GenerateRequest(url, accept, HttpMethod.Get);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
|
using Iceshrimp.Backend.Core.Federation;
|
||||||
|
using Iceshrimp.Backend.Core.Federation.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Services;
|
namespace Iceshrimp.Backend.Core.Services;
|
||||||
|
|
||||||
public class UserService(ILogger<UserService> logger, DatabaseContext db) {
|
public class UserService(ILogger<UserService> logger, DatabaseContext db, HttpClient client, ActivityPubService apSvc) {
|
||||||
private static (string Username, string Host) AcctToTuple(string acct) {
|
private static (string Username, string Host) AcctToTuple(string acct) {
|
||||||
if (!acct.StartsWith("acct:")) throw new Exception("Invalid query");
|
if (!acct.StartsWith("acct:")) throw new Exception("Invalid query");
|
||||||
|
|
||||||
|
@ -24,6 +26,10 @@ public class UserService(ILogger<UserService> logger, DatabaseContext db) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User> CreateUser(string uri, string acct) {
|
public async Task<User> CreateUser(string uri, string acct) {
|
||||||
|
logger.LogInformation("Creating user {acct} with uri {uri}", acct, uri);
|
||||||
|
var actor = await apSvc.FetchActor(uri);
|
||||||
|
logger.LogInformation("Got actor: {inbox}", actor.Inbox);
|
||||||
|
|
||||||
throw new NotImplementedException(); //FIXME
|
throw new NotImplementedException(); //FIXME
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue