From 075c22228ab3da894f50abf977f24a761a732cf8 Mon Sep 17 00:00:00 2001 From: thepra Date: Thu, 1 Oct 2026 08:05:56 +0200 Subject: [PATCH] net10, the refactor finished, and federation that works The tree had not compiled since its first commit: Group, DmGroup and IGroupUsersService were referenced and never written, an IDE rename had turned the user-settings DTO into the ViewAvatarServer enum, and the settings were saved as an entity they no longer were. Built now: - Group (an ActivityPub Group actor with its own keys, members, invitation code and optional password) and DmGroup (a conversation), with /clientapi/group/{list,insert,update,join,leave,approve}. - Posts and DMs: /clientapi/post/{list,insert,delete}, /clientapi/dm/{list,insert}; DM recipients are local usernames or user@host handles resolved by WebFinger. - Invitation sign-up and login against the group's invitation code, checking the password before any account is created. - Federation: WebFinger, NodeInfo 2.0, actors at /peasants/{name} (Person, Group, and an Application instance actor) with SPKI keys, draft-cavage RSA-SHA256 HTTP signatures both ways, an inbox handling Follow (+Accept), Undo, Create, Delete and Update, an outbox, notes at /posts/{id}, and a persisted, retried, signed delivery queue. A post to a group is announced by the group to its followers (FEP-1b12). - The unused, broken typed ActivityPub models are replaced by a renderer; NSign's HMAC setup, which could not federate, is gone. Upgrade: net10.0, MongoDB.Entities 25.1 (instance DB API, Standard GUIDs), Swashbuckle 10 / OpenApi 2, Serilog.AspNetCore 10, MailKit 4.18, PasswordGenerator 3. The JWT keys are 64 bytes (IdentityModel 8 refuses shorter for HS512). Production runs its own mongod on 127.0.0.1:27022, as Sintopia's apps do, and deploys to privapub.thepra.dev from the build runner. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB --- .gitea/workflows/build.yml | 16 + .gitea/workflows/deploy.yml | 88 ++++ Directory.Build.props | 9 + .../Group/InsertGroupForm.cs | 30 ++ PrivaPub.ClientModels/Group/JoinGroupForm.cs | 28 ++ .../Group/UpdateGroupForm.cs | 25 ++ PrivaPub.ClientModels/Group/ViewGroup.cs | 19 + PrivaPub.ClientModels/InvitationLoginForm.cs | 14 + PrivaPub.ClientModels/Post/InsertPostForm.cs | 45 ++ PrivaPub.ClientModels/Post/ViewPost.cs | 25 ++ .../PrivaPub.ClientModels.csproj | 2 +- .../Resources/FieldsNameResource.Designer.cs | 26 +- .../Resources/FieldsNameResource.it.resx | 16 +- .../Resources/FieldsNameResource.resx | 16 +- .../User/Avatar/ViewAvatar.cs | 4 +- PrivaPub.ClientModels/User/JwtUser.cs | 4 +- .../User/ViewUserSettings.cs | 25 ++ .../ClientToServer/GroupController.cs | 55 +++ .../ClientToServer/PostController.cs | 51 +++ .../ClientToServer/PrivateAvatarController.cs | 9 +- .../ClientToServer/RootUserController.cs | 200 +++++---- .../ServerToServer/PeasantsController.cs | 194 +++++---- .../ServerToServer/UsersController.cs | 63 +-- .../ServerToServer/WellKnownController.cs | 96 +++++ PrivaPub/Data/InitDb.cs | 4 +- PrivaPub/Extensions/Extensions.cs | 51 +-- .../Middleware/SocialPubConfigurations.cs | 111 +---- .../Models/ActivityPub/ActivityPubActivity.cs | 44 -- .../Models/ActivityPub/ActivityPubActor.cs | 79 ---- .../ActivityPub/ActivityPubCollection.cs | 24 -- .../ActivityPub/ActivityPubCollectionPage.cs | 18 - .../Models/ActivityPub/ActivityPubLink.cs | 28 -- .../Models/ActivityPub/ActivityPubObject.cs | 126 ------ .../ActivityPubOrderedCollection.cs | 17 - .../ActivityPubOrderedCollectionPage.cs | 27 -- .../ActivityPub/ActivityPubTombstone.cs | 14 - .../ActivityPub/Extra/ActivityPubAudience.cs | 13 - .../ActivityPub/Extra/ActivityPubIcon.cs | 19 - .../Extra/ActivityPubInstrument.cs | 13 - .../ActivityPub/Extra/ActivityPubOrigin.cs | 13 - .../ActivityPub/Extra/ActivityPubResult.cs | 13 - PrivaPub/Models/ActivityPub/MacroType.cs | 17 - PrivaPub/Models/ActivityPub/ObjectType.cs | 63 --- PrivaPub/Models/AppConfiguration.cs | 3 +- PrivaPub/Models/Federation/Delivery.cs | 18 + PrivaPub/Models/Federation/Follower.cs | 23 + PrivaPub/Models/Federation/InstanceActor.cs | 11 + PrivaPub/Models/Group/DmGroup.cs | 15 + PrivaPub/Models/Group/Group.cs | 47 +++ PrivaPub/Models/Post/DmPost.cs | 4 +- PrivaPub/Models/Post/Post.cs | 4 +- PrivaPub/Models/User/Avatar.cs | 5 +- PrivaPub/Models/socialpub.cs | 24 -- PrivaPub/PrivaPub.csproj | 53 +-- PrivaPub/Program.cs | 25 +- PrivaPub/Services/AppConfigurationService.cs | 2 +- .../Private/IPrivateAvatarUsersService.cs | 160 +++++-- .../Public/IPublicAvatarUsersService.cs | 4 +- .../Federation/ActivityPubRenderer.cs | 178 ++++++++ .../Services/Federation/DeliveryService.cs | 178 ++++++++ .../Services/Federation/HttpSignatures.cs | 183 ++++++++ PrivaPub/Services/Federation/InboxService.cs | 398 ++++++++++++++++++ .../Services/Federation/LocalActorService.cs | 201 +++++++++ .../Services/Federation/RemoteActorService.cs | 191 +++++++++ PrivaPub/Services/GroupUsersService.cs | 337 +++++++++++++++ PrivaPub/Services/IRootUsersService.cs | 4 +- PrivaPub/Services/PostsService.cs | 366 ++++++++++++++++ PrivaPub/Services/RootUsersService.cs | 119 +++--- PrivaPub/StaticServices/AuthTokenManager.cs | 4 +- PrivaPub/StaticServices/DbEntities.cs | 31 +- PrivaPub/appsettings.Development.json | 4 +- PrivaPub/appsettings.Production.json | 16 +- deploy/max/setup.sh | 53 +++ deploy/nginx/privapub-headers.conf | 4 + deploy/nginx/privapub.thepra.dev.conf | 48 +++ deploy/systemd/privapub-mongod.service | 16 + deploy/systemd/privapub.service | 20 + global.json | 7 + 78 files changed, 3419 insertions(+), 1091 deletions(-) create mode 100644 .gitea/workflows/build.yml create mode 100644 .gitea/workflows/deploy.yml create mode 100644 Directory.Build.props create mode 100644 PrivaPub.ClientModels/Group/InsertGroupForm.cs create mode 100644 PrivaPub.ClientModels/Group/JoinGroupForm.cs create mode 100644 PrivaPub.ClientModels/Group/UpdateGroupForm.cs create mode 100644 PrivaPub.ClientModels/Group/ViewGroup.cs create mode 100644 PrivaPub.ClientModels/InvitationLoginForm.cs create mode 100644 PrivaPub.ClientModels/Post/InsertPostForm.cs create mode 100644 PrivaPub.ClientModels/Post/ViewPost.cs create mode 100644 PrivaPub.ClientModels/User/ViewUserSettings.cs create mode 100644 PrivaPub/Controllers/ClientToServer/GroupController.cs create mode 100644 PrivaPub/Controllers/ClientToServer/PostController.cs create mode 100644 PrivaPub/Controllers/ServerToServer/WellKnownController.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubActivity.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubActor.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubCollection.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubCollectionPage.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubLink.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubObject.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubOrderedCollection.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubOrderedCollectionPage.cs delete mode 100644 PrivaPub/Models/ActivityPub/ActivityPubTombstone.cs delete mode 100644 PrivaPub/Models/ActivityPub/Extra/ActivityPubAudience.cs delete mode 100644 PrivaPub/Models/ActivityPub/Extra/ActivityPubIcon.cs delete mode 100644 PrivaPub/Models/ActivityPub/Extra/ActivityPubInstrument.cs delete mode 100644 PrivaPub/Models/ActivityPub/Extra/ActivityPubOrigin.cs delete mode 100644 PrivaPub/Models/ActivityPub/Extra/ActivityPubResult.cs delete mode 100644 PrivaPub/Models/ActivityPub/MacroType.cs delete mode 100644 PrivaPub/Models/ActivityPub/ObjectType.cs create mode 100644 PrivaPub/Models/Federation/Delivery.cs create mode 100644 PrivaPub/Models/Federation/Follower.cs create mode 100644 PrivaPub/Models/Federation/InstanceActor.cs create mode 100644 PrivaPub/Models/Group/DmGroup.cs create mode 100644 PrivaPub/Models/Group/Group.cs delete mode 100644 PrivaPub/Models/socialpub.cs create mode 100644 PrivaPub/Services/Federation/ActivityPubRenderer.cs create mode 100644 PrivaPub/Services/Federation/DeliveryService.cs create mode 100644 PrivaPub/Services/Federation/HttpSignatures.cs create mode 100644 PrivaPub/Services/Federation/InboxService.cs create mode 100644 PrivaPub/Services/Federation/LocalActorService.cs create mode 100644 PrivaPub/Services/Federation/RemoteActorService.cs create mode 100644 PrivaPub/Services/GroupUsersService.cs create mode 100644 PrivaPub/Services/PostsService.cs create mode 100755 deploy/max/setup.sh create mode 100644 deploy/nginx/privapub-headers.conf create mode 100644 deploy/nginx/privapub.thepra.dev.conf create mode 100644 deploy/systemd/privapub-mongod.service create mode 100644 deploy/systemd/privapub.service create mode 100644 global.json diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..fbbb448 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,16 @@ +name: Build + +on: + push: + branches: [master] + pull_request: + +jobs: + build: + name: Build + runs-on: build + steps: + - uses: actions/checkout@v4 + + - name: Build + run: dotnet build PrivaPub.sln -c Release diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..de22703 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,88 @@ +name: Deploy + +on: + workflow_dispatch: + push: + tags: + - 'v*' + +env: + UNIT: privapub + WEB_ROOT: /var/www/privapub.thepra.dev + BACKUPS: /var/backups/privapub.thepra.dev + LOCAL_URL: http://127.0.0.1:6970 + PUBLIC_URL: https://privapub.thepra.dev + +jobs: + site: + name: privapub.thepra.dev + runs-on: build + steps: + - uses: actions/checkout@v4 + + - name: Resolve the build identity + run: | + echo "BUILD_COMMIT=$(echo "$GITHUB_SHA" | cut -c1-8)" >> "$GITHUB_ENV" + echo "BUILD_REF=${GITHUB_REF_NAME:-master}" >> "$GITHUB_ENV" + echo "BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" + + - name: Publish + run: | + rm -rf "$GITHUB_WORKSPACE/publish" + dotnet publish PrivaPub/PrivaPub.csproj -c Release -r linux-x64 --self-contained true \ + -o "$GITHUB_WORKSPACE/publish" \ + -p:BuildCommit="$BUILD_COMMIT" -p:BuildRef="$BUILD_REF" -p:BuildTimeUtc="$BUILD_TIME" + + - name: Assert the publish actually produced a build + run: | + P="$GITHUB_WORKSPACE/publish" + for f in PrivaPub PrivaPub.dll PrivaPub.ClientModels.dll appsettings.Production.json Data/languagesNative.json; do + [ -e "$P/$f" ] || { echo "::error::publish output is missing $f"; exit 1; } + done + grep -q '"includedFrameworks"' "$P/PrivaPub.runtimeconfig.json" \ + || { echo "::error::publish is not self-contained"; exit 1; } + age=$(( $(date +%s) - $(stat -c %Y "$P/PrivaPub.dll") )) + [ "$age" -lt 3600 ] || { echo "::error::PrivaPub.dll is ${age}s old - the publish reused a stale artifact"; exit 1; } + echo "publish OK, $(du -sh "$P" | cut -f1)" + + - name: Snapshot the live directory + run: | + STAMP=$(date +%Y%m%d-%H%M%S) + rsync -a "$WEB_ROOT/" "$BACKUPS/site-$STAMP/" + echo "SNAPSHOT=$BACKUPS/site-$STAMP" >> "$GITHUB_ENV" + ls -1dt "$BACKUPS"/site-* 2>/dev/null | tail -n +4 | xargs -r rm -rf || true + + - name: Stop, sync, start + run: | + sudo systemctl stop "$UNIT" + rsync -a --delete --exclude 'appsettings.Development.json' "$GITHUB_WORKSPACE/publish/" "$WEB_ROOT/" + chgrp www-data "$WEB_ROOT/appsettings.Production.json" + chmod 640 "$WEB_ROOT/appsettings.Production.json" + chmod +x "$WEB_ROOT/PrivaPub" + sudo systemctl start "$UNIT" + + - name: Health check, roll back on failure + run: | + ok=0 + for i in $(seq 1 40); do + code=$(curl -s -o /dev/null -w '%{http_code}' "$LOCAL_URL/build.json" || true) + if [ "$code" = "200" ]; then ok=1; break; fi + sleep 3 + done + if [ "$ok" != "1" ]; then + echo "::error::the site did not come back healthy - rolling back to $SNAPSHOT" + sudo systemctl stop "$UNIT" + rsync -a --delete "$SNAPSHOT/" "$WEB_ROOT/" + sudo systemctl start "$UNIT" || true + exit 1 + fi + + - name: Verify what is being served + run: | + served=$(curl -fsS "$PUBLIC_URL/build.json" | python3 -c "import json,sys; print(json.load(sys.stdin).get('commit',''))") + [ "$served" = "$BUILD_COMMIT" ] || { echo "::error::served build is '$served', expected '$BUILD_COMMIT'"; exit 1; } + code=$(curl -s -o /dev/null -w '%{http_code}' -H 'Accept: application/activity+json' "$PUBLIC_URL/peasants/privapub") + [ "$code" = "200" ] || { echo "::error::the instance actor answered $code"; exit 1; } + code=$(curl -s -o /dev/null -w '%{http_code}' "$PUBLIC_URL/.well-known/nodeinfo") + [ "$code" = "200" ] || { echo "::error::nodeinfo answered $code"; exit 1; } + echo "::notice::serving $served" diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..8f315ce --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + + local + local + + + + diff --git a/PrivaPub.ClientModels/Group/InsertGroupForm.cs b/PrivaPub.ClientModels/Group/InsertGroupForm.cs new file mode 100644 index 0000000..217e6fc --- /dev/null +++ b/PrivaPub.ClientModels/Group/InsertGroupForm.cs @@ -0,0 +1,30 @@ +using PrivaPub.ClientModels.Resources; +using PrivaPub.ClientModels.ValidatorAttributes; + +using System.ComponentModel.DataAnnotations; + +namespace PrivaPub.ClientModels.Group +{ + public class InsertGroupForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string AvatarId { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + StringLength(32, MinimumLength = 3, ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource)), + RegularExpression("^[a-z0-9_]+$", ErrorMessageResourceName = "EmptySpacesNotAllowed", ErrorMessageResourceType = typeof(ErrorsResource)), + NoWhiteSpaces] + public string UserName { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + StringLength(64, ErrorMessageResourceName = "MaxLengthString", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Name { get; set; } + + [StringLength(2000, ErrorMessageResourceName = "MaxLengthString", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Description { get; set; } + + public string InvitationPassword { get; set; } + public bool IsDiscoverable { get; set; } = true; + public bool ManuallyApprovesMembers { get; set; } + } +} diff --git a/PrivaPub.ClientModels/Group/JoinGroupForm.cs b/PrivaPub.ClientModels/Group/JoinGroupForm.cs new file mode 100644 index 0000000..ca3baf9 --- /dev/null +++ b/PrivaPub.ClientModels/Group/JoinGroupForm.cs @@ -0,0 +1,28 @@ +using PrivaPub.ClientModels.Resources; + +using System.ComponentModel.DataAnnotations; + +namespace PrivaPub.ClientModels.Group +{ + public class JoinGroupForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string AvatarId { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + StringLength(Constants.GroupInvitationLength, MinimumLength = Constants.GroupInvitationLength, + ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource))] + public string InvitationCode { get; set; } + + public string InvitationPassword { get; set; } + } + + public class GroupMembershipForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string AvatarId { get; set; } + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string GroupId { get; set; } + public string MemberActorURI { get; set; } + } +} diff --git a/PrivaPub.ClientModels/Group/UpdateGroupForm.cs b/PrivaPub.ClientModels/Group/UpdateGroupForm.cs new file mode 100644 index 0000000..cf3449a --- /dev/null +++ b/PrivaPub.ClientModels/Group/UpdateGroupForm.cs @@ -0,0 +1,25 @@ +using PrivaPub.ClientModels.Resources; + +using System.ComponentModel.DataAnnotations; + +namespace PrivaPub.ClientModels.Group +{ + public class UpdateGroupForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string AvatarId { get; set; } + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string GroupId { get; set; } + + [StringLength(64, ErrorMessageResourceName = "MaxLengthString", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Name { get; set; } + [StringLength(2000, ErrorMessageResourceName = "MaxLengthString", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Description { get; set; } + + public string InvitationPassword { get; set; } + public bool RemoveInvitationPassword { get; set; } + public bool RegenerateInvitationCode { get; set; } + public bool? IsDiscoverable { get; set; } + public bool? ManuallyApprovesMembers { get; set; } + } +} diff --git a/PrivaPub.ClientModels/Group/ViewGroup.cs b/PrivaPub.ClientModels/Group/ViewGroup.cs new file mode 100644 index 0000000..9a91fff --- /dev/null +++ b/PrivaPub.ClientModels/Group/ViewGroup.cs @@ -0,0 +1,19 @@ +namespace PrivaPub.ClientModels.Group +{ + public class ViewGroup + { + public string Id { get; set; } + public string UserName { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Url { get; set; } + public string Handle { get; set; } + public bool IsDiscoverable { get; set; } + public bool ManuallyApprovesMembers { get; set; } + public bool IsOwner { get; set; } + public string InvitationCode { get; set; } + public bool IsPasswordRequired { get; set; } + public int MembersCount { get; set; } + public DateTime CreationDate { get; set; } + } +} diff --git a/PrivaPub.ClientModels/InvitationLoginForm.cs b/PrivaPub.ClientModels/InvitationLoginForm.cs new file mode 100644 index 0000000..b1da56c --- /dev/null +++ b/PrivaPub.ClientModels/InvitationLoginForm.cs @@ -0,0 +1,14 @@ +using PrivaPub.ClientModels.Resources; + +using System.ComponentModel.DataAnnotations; + +namespace PrivaPub.ClientModels +{ + public class InvitationLoginForm : LoginForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + StringLength(Constants.GroupInvitationLength, MinimumLength = Constants.GroupInvitationLength, + ErrorMessageResourceName = "StringLengthMinMax", ErrorMessageResourceType = typeof(ErrorsResource))] + public string InvitationCode { get; set; } + } +} diff --git a/PrivaPub.ClientModels/Post/InsertPostForm.cs b/PrivaPub.ClientModels/Post/InsertPostForm.cs new file mode 100644 index 0000000..546566c --- /dev/null +++ b/PrivaPub.ClientModels/Post/InsertPostForm.cs @@ -0,0 +1,45 @@ +using PrivaPub.ClientModels.Resources; + +using System.ComponentModel.DataAnnotations; + +namespace PrivaPub.ClientModels.Post +{ + public class InsertPostForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string AvatarId { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + StringLength(5000, ErrorMessageResourceName = "MaxLengthString", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Text { get; set; } + + [StringLength(200, ErrorMessageResourceName = "MaxLengthString", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Title { get; set; } + + public string GroupId { get; set; } + public string AnsweringToPostId { get; set; } + public bool HasContentWarning { get; set; } + } + + public class InsertDmForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string AvatarId { get; set; } + + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)), + StringLength(5000, ErrorMessageResourceName = "MaxLengthString", ErrorMessageResourceType = typeof(ErrorsResource))] + public string Text { get; set; } + + public string DmGroupId { get; set; } + public List Recipients { get; set; } = new();//local usernames or user@host handles + public bool HasContentWarning { get; set; } + } + + public class DeletePostForm + { + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string AvatarId { get; set; } + [Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource))] + public string PostId { get; set; } + } +} diff --git a/PrivaPub.ClientModels/Post/ViewPost.cs b/PrivaPub.ClientModels/Post/ViewPost.cs new file mode 100644 index 0000000..66890b2 --- /dev/null +++ b/PrivaPub.ClientModels/Post/ViewPost.cs @@ -0,0 +1,25 @@ +namespace PrivaPub.ClientModels.Post +{ + public class ViewPost + { + public string Id { get; set; } + public string ObjectURI { get; set; } + public string AuthorAvatarId { get; set; } + public string AuthorActorURI { get; set; } + public string GroupId { get; set; } + public string DmGroupId { get; set; } + public string AnsweringToPostId { get; set; } + public string Title { get; set; } + public string Text { get; set; } + public bool HasContentWarning { get; set; } + public bool IsFederatedCopy { get; set; } + public DateTime CreationDate { get; set; } + } + + public class ViewDmGroup + { + public string Id { get; set; } + public List Members { get; set; } = new(); + public DateTime UpdatedAt { get; set; } + } +} diff --git a/PrivaPub.ClientModels/PrivaPub.ClientModels.csproj b/PrivaPub.ClientModels/PrivaPub.ClientModels.csproj index 53b2676..44c8ab4 100644 --- a/PrivaPub.ClientModels/PrivaPub.ClientModels.csproj +++ b/PrivaPub.ClientModels/PrivaPub.ClientModels.csproj @@ -1,7 +1,7 @@ - net7.0 + net10.0 enable diff --git a/PrivaPub.ClientModels/Resources/FieldsNameResource.Designer.cs b/PrivaPub.ClientModels/Resources/FieldsNameResource.Designer.cs index 69e6c54..b309584 100644 --- a/PrivaPub.ClientModels/Resources/FieldsNameResource.Designer.cs +++ b/PrivaPub.ClientModels/Resources/FieldsNameResource.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -396,6 +396,30 @@ namespace PrivaPub.ClientModels.Resources { /// /// Looks up a localized string similar to Name. /// + public static string AvatarId { + get { + return ResourceManager.GetString("AvatarId", resourceCulture); + } + } + + public static string Biography { + get { + return ResourceManager.GetString("Biography", resourceCulture); + } + } + + public static string LanguageCode { + get { + return ResourceManager.GetString("LanguageCode", resourceCulture); + } + } + + public static string UserName { + get { + return ResourceManager.GetString("UserName", resourceCulture); + } + } + public static string Name { get { return ResourceManager.GetString("Name", resourceCulture); diff --git a/PrivaPub.ClientModels/Resources/FieldsNameResource.it.resx b/PrivaPub.ClientModels/Resources/FieldsNameResource.it.resx index 9415499..3f1bccc 100644 --- a/PrivaPub.ClientModels/Resources/FieldsNameResource.it.resx +++ b/PrivaPub.ClientModels/Resources/FieldsNameResource.it.resx @@ -1,4 +1,4 @@ - +