Compare commits

..

242 Commits

Author SHA1 Message Date
Natty 5363a0c137
Offload some blocking tasks to worker threads
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-25 22:48:27 +01:00
Natty 5666bb4622
Frontend: Fixed prepending items when fresh and full
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-25 19:39:05 +01:00
Natty 8aa2a4dac4
Frontend: SSE and pagination fixes
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-25 17:20:22 +01:00
Natty ff6458ea2e
Fixed a typo
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-17 18:47:51 +01:00
Natty ad253fb02b
Better AP delivery timeout behavior
ci/woodpecker/push/ociImagePush Pipeline failed Details
2024-11-17 18:33:12 +01:00
Natty 11ff0ad2c0
Fixed error serialization
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-17 04:25:30 +01:00
Natty 0a23953ae9
Report proper RPC errors
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-17 03:56:46 +01:00
Natty 2acc41587a
Drop RPC connections receiving bogus data
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-16 19:29:46 +01:00
Natty 56f80f290a
Dependency management 2024-11-16 17:50:51 +01:00
Natty d598387795
Recycle the read buffers for RPC data 2024-11-16 17:43:57 +01:00
Natty 52cced9537
Allow concurrent RPC calls on the server
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-16 03:34:43 +01:00
Natty c10594bf7e
Use a caching DNS resolver
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-16 01:45:59 +01:00
Natty 369df105c9
Relaxed AP POST headers and response type
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-16 00:48:56 +01:00
Natty e53afb3313
Print RPC errors in Debug instead of Display
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-16 00:11:18 +01:00
Natty ad3b0e1ffb
Report RPC errors
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-15 23:43:01 +01:00
Natty 51375e2ded
Removed unsafe RPC reads
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-15 23:05:19 +01:00
Natty 531b10ef85
Fixed config and packet buffer size limits
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-15 22:36:32 +01:00
Natty 845fcb385b
Actually fixed the lockfile
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-11-15 21:22:30 +01:00
Natty e4beba0aea
Updated lockfile to a non-nightly version 💀
ci/woodpecker/push/ociImagePush Pipeline failed Details
2024-11-15 21:10:16 +01:00
Natty 161996632b
Updated lockfile
ci/woodpecker/push/ociImagePush Pipeline failed Details
2024-11-15 20:55:52 +01:00
Natty 5c568b1657
Updated Rust in Dockerfile
ci/woodpecker/push/ociImagePush Pipeline failed Details
2024-11-15 20:49:08 +01:00
Natty 5fb85e0db6
Implemented AP fetching via RPC
ci/woodpecker/push/ociImagePush Pipeline failed Details
2024-11-15 20:39:13 +01:00
Natty 80c5bf8ae6
Fixed RPC and implemented responses 2024-11-14 17:33:33 +01:00
Natty b9160305f1
Streamlined error handling 2024-11-13 13:41:06 +01:00
Natty 9c42b20fa9
Implemented rudimentary RPC 2024-11-12 22:37:18 +01:00
Natty 69c126d860
Code cleanup 2024-11-12 22:28:55 +01:00
Natty 7581ecf331
Code cleanup 2024-11-12 22:22:32 +01:00
Natty 766fd8ea7d
Added a service for generating IDs 2024-11-12 22:20:57 +01:00
Natty 5241b18b0d
Also cache the local user key pair 2024-11-12 22:08:18 +01:00
Natty 88df8eca55
Implemented key parsing 2024-11-12 22:00:37 +01:00
Natty 62fc36ff03
Updated comment 2024-09-06 00:04:28 +02:00
Natty 611afc591c
Split query into two due to SeaORM limitations
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-09-05 23:40:12 +02:00
Natty 09f564746f
SeaORM magic to use CTEs for pins
ci/woodpecker/push/ociImagePush Pipeline was successful Details
Might or might not work
2024-09-05 22:16:54 +02:00
Natty 607cb2ded2
Clarified tables in subselect
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-09-05 19:33:57 +02:00
Natty aebc9803db
Wrong column, really wrong
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-09-05 18:48:29 +02:00
Natty cb9bf082f4
Fixed dependencies in the frontend
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-09-05 17:46:17 +02:00
Natty 87e9fb36e1
Fixed some past crimes
ci/woodpecker/push/ociImagePush Pipeline failed Details
Mainly fetching pins in a subquery instead of joining

Parsing needs a bigger overhaul
2024-09-05 17:22:54 +02:00
Natty 1173360265
Updated and fixed dependencies 2024-09-05 15:58:42 +02:00
Natty 78c93f3c20
Fixed parsing complexity issues in MMM
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-05-22 03:42:50 +02:00
Natty b74f2d69a4
Reintroduced graceful shutdown
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-05-20 17:23:08 +02:00
Natty e06906dd6e
Also fetch profile in cache 2024-04-30 16:02:35 +02:00
Natty 155e458806
Frontend: Code and post form cleanup 2024-04-30 13:52:13 +02:00
Natty ea4751400b
Frontend: Made proxy loading placeholders actually take up space
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-30 00:53:28 +02:00
Natty 7c44e55597
Frontend: Prettier loading icons
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-04-30 00:17:20 +02:00
Natty 6c7e3d79e0
Frontend: Prettier notification groups 2024-04-29 21:49:42 +02:00
Natty bca16253af
Frontend: Fixed audio player
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-29 21:13:34 +02:00
Natty 1f10156ebb
The client module is irrelevant 2024-04-29 18:36:20 +02:00
Natty aeb94687b5
Local users are not supposed to be fetched by URI 2024-04-29 18:27:10 +02:00
Natty 553dbb9b7b
Always send RSA256-signed requests 2024-04-29 18:26:39 +02:00
Natty e21c5a8132
Merge branch 'activity-pub' into development
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/main.rs
2024-04-17 13:52:14 +02:00
Natty 8b6bdd6e02
Made the video player work better on mobile
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-16 03:49:18 +02:00
Natty b5812de552
Removed the glob-stream stack overflow workaround
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-15 05:03:46 +02:00
Natty 4f4a871ae5
Fixed a type system oopsie in NodeInfo
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-15 04:56:44 +02:00
Natty e1fd078367
Simplified the model naming 2024-04-15 04:45:44 +02:00
Natty ce46782318
Implemented outbound request signing 2024-04-09 16:49:00 +02:00
Natty aefef079a7
Unfucked the frontend
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-04-08 03:10:23 +02:00
Natty 156ad5c499
Refactored MkDateSeparatedList 2024-04-07 18:41:44 +02:00
Natty 13e5d447e8
Expanded accepted JSON-LD content types 2024-04-07 04:31:20 +02:00
Natty db444adcc1
Fixed dependencies
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-07 04:29:12 +02:00
Natty ff3f0927fb
Magnetar stream status for notification view
ci/woodpecker/push/ociImagePush Pipeline failed Details
2024-04-07 04:12:27 +02:00
Natty d12b65fff7
Fixed useTooltip for elements that already exist at setup
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-07 02:08:24 +02:00
Natty 812d4ee6b2
Fixed more accidental array mutations
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-05 23:33:33 +02:00
Natty 8618b2084d
Notification and pagination bugfixes 2024-04-05 23:22:22 +02:00
Natty 014b394f66
Fixed a regression in WebFinger by tag
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-01 05:09:09 +02:00
Natty 0dff0c0785
Implemented WebFinger flow and fixed incoming WebFinger
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-04-01 01:34:27 +02:00
Natty ffed556107
Fixed Dockerfile
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-03-31 05:25:20 +02:00
Natty 5aceca72bd
Implemented host-meta and some overdue fixes
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-03-31 05:10:48 +02:00
Natty a640890cad
Fixed an edge case in strict flanking rules
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-03-13 14:31:05 +01:00
Natty 799730bedc
Fixed #7 and #8
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-03-13 14:02:34 +01:00
Natty b295c66f0c
Simplied async code 2024-03-12 19:32:31 +01:00
Natty 66ce0de3fa
Fixed follow list view pagination
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-03-06 16:11:37 +01:00
Natty 236502ad05
Better tracing of API endpoints 2024-03-06 16:11:35 +01:00
Natty cbf6c7fe43
Updated to-do list 2024-03-06 16:11:32 +01:00
Natty ea0d681e5c
Fixed a follows_you label doing the opposite of what it says 2024-03-06 16:11:21 +01:00
Natty dc86b4e67d
Frontend: Fixed the notification groups always showing the first user for reactions
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-02-29 23:58:51 +01:00
Natty 7f0be08799
Update group ID and time when splicing
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-02-29 23:47:01 +01:00
Natty 1394557f81
Implemented client-side notification grouping
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-02-29 23:40:27 +01:00
Natty 459f944243
Renamed the notification settings component to Mag 2024-02-29 02:13:21 +01:00
Natty a169ebb004
Fixed notification filtering and a bunch of bugs
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-02-29 00:27:19 +01:00
Natty 7c2909bceb
Made thread note refs deep again
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-02-28 03:30:53 +01:00
Natty 8d9415b3ab
Slicing the wrong buffer
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-02-28 02:48:02 +01:00
Natty d64d34b2ef
Fixed the renotes tab in note view
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-02-28 02:22:45 +01:00
Natty 169e85dead
Fixed note rendering issues and added debouncing for timeline notes
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-02-28 02:14:38 +01:00
Natty 16739635d1
Fixed the float on right-side buttons in the post form
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-02-22 18:22:16 +01:00
Natty 7bfb5c0f91
Cleaner post form
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-02-18 16:31:35 +01:00
Natty c8d658f807
Added a federation HTTP client and basic host-meta implementation 2024-02-17 15:04:18 +01:00
Natty 5430fc81f4
Fixed repo link in the frontend 2024-02-17 14:46:04 +01:00
Natty f441de806f
Frontend: Basic notification receiving via SSE
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-18 03:26:26 +01:00
Natty 7b02f84271
Basic backend SSE notification implemention 2024-01-17 00:41:32 +01:00
Natty ad3528055f
Basic notification fetching via Magnetar 2024-01-16 18:10:56 +01:00
Natty 98fb2ef0d8
Drop map values that are undefined from API requests 2024-01-16 18:09:28 +01:00
Natty 94cff7c2c8
Skip non-cacheable scenarios 2024-01-16 13:04:53 +01:00
Natty 2e4903e603
Fetch quotes in post-processing
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-15 19:31:51 +01:00
Natty b9f0f33b3c
Resolve avatar and banner separately
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-15 18:14:35 +01:00
Natty 185262eb05
Enable user detail resolving
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-15 01:46:24 +01:00
Natty 475c33e57e
Notification fetching base 2024-01-15 01:46:08 +01:00
Natty 3c16574d03
Disable AI IDE features 2024-01-15 01:40:40 +01:00
Natty a51e412cb8
Frontend: Fixed media preview JS gallery 2024-01-13 03:58:27 +01:00
Natty ff00dfebb6
Removed gallery tables and entity cleanup
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-13 00:55:55 +01:00
Natty 364074aecc
Frontend: Removed the gallery feature 2024-01-13 00:47:38 +01:00
Natty d16f9d704d
Frontend: Removed unused endpoint 2024-01-13 00:31:36 +01:00
Natty ce1bfc0ee1
Dependency cleanup 2024-01-12 23:47:19 +01:00
Natty 4dd3cd59ac
Renote resolve depth 2 for renotes of quote notes 2024-01-12 23:44:12 +01:00
Natty 13f5ad3672
Removed the page tables 2024-01-12 23:13:00 +01:00
Natty 789852211b
Frontend: Fixed follow request layout issues 2024-01-12 13:45:45 +01:00
Natty fdf9966ef6
Frontend: Fixed pagination bugs
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-12 04:47:00 +01:00
Natty 4834daceec
Frontend: Magnetar endpoint pagination
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-12 03:56:22 +01:00
Natty 80a29f771f
Frontend: Removed AiScript plugins and pages
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-10 23:35:51 +01:00
Natty 2fea3b79a7
Frontend: Removed the button and AiScript widgets 2024-01-10 21:35:59 +01:00
Natty 22208d4c68
Frontend: Removed the click-anime Vue directive 2024-01-10 21:27:03 +01:00
Natty b2a55f1d6d
Frontend: Removed the server metrics widget 2024-01-10 21:15:30 +01:00
Natty 1366adcebf
Frontend: Removed pointless tag cloud eye candy 2024-01-10 21:11:41 +01:00
Natty f06c74c85c
Serialize time as a millis epoch timestamp in cursors 2024-01-10 03:09:14 +01:00
Natty b2543b29d3
Updated TS types and added support
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-09 23:34:14 +01:00
Natty 1bef42ead5
Implemented pagination and user follower listing 2024-01-09 22:29:06 +01:00
Natty a658452138
Refactored aliases
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-08 20:15:51 +01:00
Natty 3b1eab8069
Apply visibility checks to parent notes and renote targets
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-08 04:30:12 +01:00
Natty 811fecf99a
Fixed FROM and filtering issues
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-08 01:14:19 +01:00
Natty c6d84d6287
Added is_renote
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-08 00:07:21 +01:00
Natty 024cdef68c
Fixed TypeScript types for magnetar-common
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-07 23:38:23 +01:00
Natty deb7f6ef5e
Refactored note fetching
ci/woodpecker/push/ociImagePush Pipeline failed Details
2024-01-07 23:28:53 +01:00
Natty 074c6f999e
Computed is_quote node column 2024-01-07 23:18:19 +01:00
Natty c2cfd7e007
Removed user groups
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-07 02:44:52 +01:00
Natty 9f633d0749
Actually use the renote reply
ci/woodpecker/push/ociImagePush Pipeline is running Details
2024-01-05 00:11:04 +01:00
Natty bbc4f84ceb
Add note processing for parents of renote posts and introduced better timeline filtering
ci/woodpecker/push/ociImagePush Pipeline is pending Details
2024-01-04 23:52:24 +01:00
Natty 152c4e6fc6
Fixed CI workflow
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-03 20:58:28 +01:00
Natty 93caa314c9
Frontend: Key on the notification sliders 2024-01-03 20:55:46 +01:00
Natty 15b0ea8e26
Frontend: Fixed i18 import
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-03 03:50:12 +01:00
Natty 42bf912bb9
Frontend: Fixed pagination queuing behavior
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-03 03:35:41 +01:00
Natty ab18633fbc
Frontend: Refactored pagination
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-02 03:35:24 +01:00
Natty 6003f5e404
Frontend: Pagination by time and removed ads
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2024-01-01 20:29:33 +01:00
Natty 6b96f60b43
MMM: Fixed nesting counting in increase_nesting 2023-12-31 17:18:27 +01:00
Natty ce8cb5aad0
MMM: More forgiving math parsing 2023-12-30 20:02:25 +01:00
Natty 9c8489ced7
MMM: Correctly parse empty strings
Surely I wouldn't forget to do that, right?
2023-12-30 19:23:32 +01:00
Natty 64350cfcb8
Frontend: Fixed showing media in profiles
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-12-28 04:00:00 +01:00
Natty b50249da02
Frontend: Changed powered by to "Magnetar"
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-12-28 00:35:10 +01:00
Natty 7fbcc4ec19
Frontend: Removed dependence on Calckey note resolving and avatars 2023-12-28 00:34:52 +01:00
Natty 05469f68a8
User and note fetching fixes
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-12-27 04:40:13 +01:00
Natty 69cb1c5220
Render all notes via Magnetar
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-12-27 03:50:56 +01:00
Natty 76c4d0267f
Frontend: Switched to MMM rendering where possible
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-12-24 23:46:36 +01:00
Natty 7ad46191ec
Updated Dockerfile to Rust 1.74
ci/woodpecker/push/ociImagePush Pipeline failed Details
2023-12-23 01:36:44 +01:00
Natty bf2d475f78
Frontend: Fixed isRenote in MkNote
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-12-22 23:31:57 +01:00
Natty 72ed3391fc
Frontend: Fixed note rendering for renote notes
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-12-22 21:25:00 +01:00
Natty 10bddd886e
Frontend: Display missing emoji as raw text 2023-12-22 20:23:23 +01:00
Natty c3e7791b3e
Backend: Fixed emoji context merging 2023-12-22 19:52:38 +01:00
Natty f23aae4ac7
Frontend: Fixed note previews with custom emojis 2023-12-22 19:30:46 +01:00
Natty cb8e42d219
Updated Magnetar to Axum 0.7 2023-12-22 19:19:07 +01:00
Natty f55ff2b761
Frontend: Natty doesn't know how to icon
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-19 17:03:01 +01:00
Natty 73fa4d2884
Frontend: Fixed a bunch of jank
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-19 15:13:34 +01:00
Natty e2cab2aa9b
Frontend: Fixed reactions overriding the self reaction flag
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-15 01:16:34 +01:00
Natty 79660fb816
Fixed user relation packing
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-09 21:53:03 +01:00
Natty 81d0c678d8
Full user packing in the backend and Magnetar-filled preview cards in the frontend
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-09 21:35:55 +01:00
Natty d1ca62a807
Frontend: Fixed the null error in tooltips 2023-11-09 17:17:46 +01:00
Natty 10256c683c
Frontend: Fixed note renote detection 2023-11-09 16:34:41 +01:00
Natty 7eb03d59fb
Frontend: Fixed parent note visibility inheritance in the post form
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-08 02:28:10 +01:00
Natty 4f6e6163cc
Ported MkAvatars to Magnetar
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-07 21:15:35 +01:00
Natty e1859c98bd
Frontend: Fixed quote visibility checking
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-07 17:20:13 +01:00
Natty 3b8997d153
Frontend: Fixed isRenote
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-07 16:28:48 +01:00
Natty 2173ee452f
Fixed visibility display 2023-11-07 16:28:15 +01:00
Natty 0ed23fe68e
Fixed pulling emoji from the shortcode property
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-07 03:35:59 +01:00
Natty 0bb0c775dc
Emoji reaction handling fixes
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-07 03:11:22 +01:00
Natty 4cb7431681
Frontend: Fixed renote visibilities not appearing 2023-11-06 18:07:00 +01:00
Natty e53e274d1d
Frontend: Fixed more reaction bugs
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-06 02:54:02 +01:00
Natty cb645ef0f6
Frontend: Hopefully fixed emoji resolution
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-06 02:09:06 +01:00
Natty b4611bda78
Frontend: Undo isActive v-model
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-06 01:07:39 +01:00
Natty 0ed06ee68f
Frontend: Fixed reactions and invalid access
ci/woodpecker/push/ociImagePush Pipeline failed Details
2023-11-06 00:55:18 +01:00
Natty 3efc376f2b
Frontend: Magnetar emoji URL hinting
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-06 00:18:16 +01:00
Natty 7eed5d6b62
Updated runtime image to Debian Bookworm
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-05 22:59:38 +01:00
Natty b801ab83ac
Fixed the Dockerfile 2023-11-05 22:48:55 +01:00
Natty fdfd3163b1
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline failed Details
# Conflicts:
#	fe_calckey/frontend/package.json
2023-11-05 20:41:00 +01:00
Natty e7812a816b
Note fetching from Magnetar! 2023-11-05 20:39:28 +01:00
Natty 0ad015cd68
Frontend: Initial BE API client integration 2023-11-05 15:28:55 +01:00
Natty 6908a2f350
Proxied images and user instance meta resolving 2023-11-05 15:23:48 +01:00
Natty a5ab2acca0
TS_RS Rust and TypeScript types should now be in parity 2023-11-03 21:14:17 +01:00
Natty c8627a996c
Undo an accidental commit of the linker setting 2023-11-03 19:56:29 +01:00
Natty 2c3d675392
First attempt at TS_RS 2023-11-03 13:17:53 +01:00
Natty 5a8dc04915
Fixed TS_RS 2023-11-02 20:40:12 +01:00
Natty a34829c63d
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-11-02 00:22:30 +01:00
Natty cf04146d2f
Poll fetching 2023-11-02 00:22:04 +01:00
Natty 5b9b813037
Self reaction and renote fetching 2023-11-01 22:55:59 +01:00
Natty 970644ffc7
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-31 01:24:52 +01:00
Natty 8e02e46be5
User fetching with reactions and a user by tag endpoint 2023-10-30 23:00:46 +01:00
Natty f4fa5925f7
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-29 17:06:11 +01:00
Natty 734ace5d05
Note context fetching 2023-10-29 17:05:42 +01:00
Natty 97407097b7
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-29 12:54:08 +01:00
Natty 7dc38ada9a
MMM: Fixed hashtag serialization 2023-10-29 12:52:20 +01:00
Natty acdc3e8bc1
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-29 02:11:10 +01:00
Natty 0755dac002
Note by ID testing endpoint 2023-10-29 02:10:48 +01:00
Natty 4bbc368f92
Note filtering by time frame 2023-10-29 01:40:03 +02:00
Natty 3cd43d840a
Basic note fetching 2023-10-29 01:27:32 +02:00
Natty 771795d81f
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-28 00:33:34 +02:00
Natty f0e56deca9
Generic caching and basic user fetching in the backend 2023-10-28 00:33:09 +02:00
Natty 5572695515
Cached emoji resolution 2023-10-27 21:55:08 +02:00
Natty 18d526cf8c
Instance meta cache and initial pack processing for users 2023-10-27 01:41:48 +02:00
Natty fc86f0e29c
Backend: Prep for MMM parser processing 2023-10-26 21:37:15 +02:00
Natty 42e68fffcd
MMM: Matrix handle parsing 2023-10-26 21:23:59 +02:00
Natty f34be3a104
MMM: Profile field parsing and skipping Matrix handles 2023-10-26 21:08:51 +02:00
Natty 216f4229fc
Merge branch 'development' 2023-10-26 19:29:43 +02:00
Natty c6a282c26e
MMM: Coverage and naming fix 2023-10-26 18:38:45 +02:00
Natty 91883c6b36
MMM: XML serialization and fixed block code parsing 2023-10-26 00:30:11 +02:00
Natty 6ed6066b1f
MMM: Reexport in the SDK 2023-10-25 19:45:59 +02:00
Natty 146c072c92
Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-24 00:30:44 +02:00
Natty c4a8ebebf3
MMM: Janky outer flanking rules implementation 2023-10-24 00:27:54 +02:00
Natty 42fa83c6e2
MMM: Fixed hashtag parsing 2023-10-23 23:52:02 +02:00
Natty 86d5c87e9a
MMM: Nesting-limited parsing 2023-10-16 23:45:45 +02:00
Natty 23a63f2fe9
MMM: Made the parser always output owned tokens 2023-10-14 21:41:36 +02:00
Natty 68abd4f787
Frontend: Align nameColumn to baseline
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-11 01:56:29 +02:00
Natty 8ee5ee8bc0
Frontend: Nicer wrapping for the nameColumn
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-11 01:38:49 +02:00
Natty 03be3d02e3
Frontend: New codename :3
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-11 00:43:58 +02:00
Natty 38753bab28
Frontend: Fixed approve/deny for narrow layouts 2023-10-11 00:41:42 +02:00
Natty d0d977e6eb
Fixed URL parsing and initial flanking rules implementation 2023-10-08 22:15:55 +02:00
Natty 26bd6fe4b2
Normalized tests 2023-10-07 21:26:25 +02:00
Natty c4fd99fa45
Stricter URL parsing 2023-10-07 21:22:21 +02:00
Natty d2bc679740
Fixed link parsing 2023-10-07 20:40:01 +02:00
Natty 154cc27c07
More precise emoji extraction and fixed center tag parsing 2023-10-07 19:44:39 +02:00
Natty 95bce443be
Fixed a code typo 2023-10-07 01:53:03 +02:00
Natty 703e1191c2
Janky sequence unnesting and attempting to salvage nested parsing in incorrect formatting tags 2023-10-07 01:46:20 +02:00
Natty 453891ddf4
Connected it all 2023-10-06 00:17:52 +02:00
Natty c45ec852dd
Shortcode emoji parsing 2023-10-05 22:32:53 +02:00
Natty 4431a3ad62
Code block parsing 2023-10-05 22:25:29 +02:00
Natty a6ee6bfbde
Plain tag parsing 2023-10-05 22:12:51 +02:00
Natty 7c8e65f556
Hashtag parsing 2023-10-05 22:02:46 +02:00
Natty 8009546bfe
Emoji parsing 2023-10-05 21:21:23 +02:00
Natty 52dc491a47
Mention parsing 2023-10-05 20:05:03 +02:00
Natty 9b26691ff4
Implemented URL parsing 2023-10-05 19:09:26 +02:00
Natty 24d44632e0
Minor cleanup 2023-10-04 19:44:27 +02:00
Natty 46e0766a36
Implemented MFM functions and math and center blocks 2023-10-04 19:31:03 +02:00
Natty 1af8f4e213
Basic inline tag parsing 2023-10-04 16:29:35 +02:00
Natty 95141388fa
Created a project for the MMM parser 2023-10-04 16:29:35 +02:00
Natty a8636947b9
Version unification
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-02 19:11:35 +02:00
Natty be08be61c6
Frontend: Removed blinking indicators 2023-10-02 19:05:30 +02:00
Natty c58bc2994d
Updated dev hostname in Caddyfile 2023-10-02 19:05:10 +02:00
Natty fc0ec19afb
Frontend: Localization cleanup 2023-10-02 19:04:47 +02:00
Natty a0bdab23e7
Frontend: Fixed document titles
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-10-01 01:25:20 +02:00
Natty b4e01fee63
Frontend: Alt text indicators 2023-10-01 01:05:18 +02:00
Natty 36c3507d84
Frontend: Replaced the refreshing text with a spinner
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 23:03:48 +02:00
Natty 2525b3a50a
Frontend: Graceful shutdown of backend 2023-09-30 23:02:39 +02:00
Natty 3837980e50
Frontend: Fixed imports (???)
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 22:40:58 +02:00
Natty 5c8db9b243
Frontend: Cleaned up poll code
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 22:16:27 +02:00
Natty 98c5bcb4c4
Frontend: Different icon for already-renoted buttons
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 21:31:00 +02:00
Natty df275de905
Frontend: Fixed a wrong renote cancel condition 2023-09-30 21:16:56 +02:00
Natty dde74dd56b
Frontend: Fixed renote button
ci/woodpecker/push/ociImagePush Pipeline was successful Details
2023-09-30 21:03:16 +02:00
828 changed files with 30306 additions and 26750 deletions

View File

@ -1,2 +1,2 @@
[registries.crates-io]
protocol = "sparse"
protocol = "sparse"

View File

@ -1,7 +1,11 @@
nattyarch.local {
magnetar-dev.local {
log {
}
handle /.well-known/host-meta {
reverse_proxy 127.0.0.1:4939
}
handle /.well-known/webfinger {
reverse_proxy 127.0.0.1:4939
@ -20,7 +24,7 @@ nattyarch.local {
}
@render_html {
not path /api* /proxy* /files* /avatar* /identicon* /streaming
not path /api* /proxy* /files* /avatar* /identicon*
header Accept text/html*
}

View File

@ -1,4 +1,4 @@
pipeline:
steps:
publish-docker-latest:
image: docker.io/plugins/kaniko
settings:
@ -12,7 +12,5 @@ pipeline:
from_secret: docker_password
when:
event: [push]
branch: [main]
branches: [main]
- event: push
branch: main

3026
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,101 +8,142 @@ license = "AGPL-3.0-only"
[workspace]
members = [
".",
"ext_activity_pub",
"ext_activity_streams",
"ext_federation",
"ext_nodeinfo",
"ext_webfinger",
"ext_calckey_model",
"ext_model",
"fe_calckey",
"magnetar_common",
"magnetar_runtime",
"magnetar_sdk",
"core"
"magnetar_mmm_parser",
"core",
]
[workspace.package]
version = "0.2.0"
version = "0.3.0-alpha"
edition = "2021"
[workspace.dependencies]
async-trait = "0.1"
axum = "0.6"
cached = "0.46"
async-stream = "0.3"
axum = "0.7"
axum-extra = "0.9"
base64 = "0.22"
bytes = "1.7"
cached = "0.54"
cfg-if = "1"
chrono = "0.4"
compact_str = "0.8"
dotenvy = "0.15"
ed25519-dalek = "2.1"
either = "1.9"
emojis = "0.6"
futures = "0.3"
futures-channel = "0.3"
futures-core = "0.3"
futures-util = "0.3"
headers = "0.3"
http = "0.2"
hyper = "0.14"
js-sys = "0.3"
log = "0.4"
miette = "5.9"
headers = "0.4"
http = "1.0"
httpdate = "1"
hyper = "1.1"
idna = "1"
indexmap = "2.2"
itertools = "0.13"
kdl = "4"
lru = "0.12"
miette = "7"
nom = "7"
nom_locate = "4"
percent-encoding = "2.2"
redis = "0.23"
reqwest = "0.11"
sea-orm = "0.12"
sea-orm-migration = "0.12"
priority-queue = "2.0"
quick-xml = "0.36"
redis = "0.26"
regex = "1.9"
rmp-serde = "1.3"
rsa = "0.9"
reqwest = "0.12"
sea-orm = "1"
sea-orm-migration = "1"
serde = "1"
serde_json = "1"
strum = "0.25"
serde_urlencoded = "0.7"
sha2 = "0.10"
smallvec = "1.13"
strum = "0.26"
tera = { version = "1", default-features = false }
thiserror = "1"
thiserror = "2"
tokio = "1.24"
tokio-util = "0.7"
tokio-stream = "0.1"
toml = "0.8"
tower = "0.4"
tower-http = "0.4"
tower = "0.5"
tower-http = "0.6"
tracing = "0.1"
tracing-subscriber = "0.3"
ts-rs = "7"
ulid = "1"
unicode-segmentation = "1.10"
url = "2.3"
walkdir = "2.3"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = "0.3"
[dependencies]
magnetar_activity_pub = { path = "./ext_activity_pub" }
magnetar_core = { path = "./core" }
magnetar_common = { path = "./magnetar_common" }
magnetar_federation = { path = "./ext_federation" }
magnetar_host_meta = { path = "./ext_host_meta" }
magnetar_webfinger = { path = "./ext_webfinger" }
magnetar_nodeinfo = { path = "./ext_nodeinfo" }
magnetar_calckey_model = { path = "./ext_calckey_model" }
magnetar_model = { path = "./ext_model" }
magnetar_runtime = { path = "./magnetar_runtime" }
magnetar_sdk = { path = "./magnetar_sdk" }
cached = { workspace = true }
lru = { workspace = true }
chrono = { workspace = true }
dotenvy = { workspace = true }
async-trait = { workspace = true }
axum = { workspace = true, features = ["macros", "headers"] }
futures-util = { workspace = true }
axum = { workspace = true, features = ["macros"] }
axum-extra = { workspace = true, features = ["typed-header"] }
async-stream = { workspace = true }
headers = { workspace = true }
hyper = { workspace = true, features = ["full"] }
reqwest = { workspace = true, features = ["hickory-dns"] }
tokio = { workspace = true, features = ["full"] }
tokio-stream = { workspace = true }
tower = { workspace = true }
tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
ulid = { workspace = true }
url = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing = { workspace = true }
cfg-if = { workspace = true }
bytes = { workspace = true }
compact_str = { workspace = true }
either = { workspace = true }
futures = { workspace = true }
futures-util = { workspace = true }
itertools = { workspace = true }
miette = { workspace = true, features = ["fancy"] }
strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
miette = { workspace = true }
percent-encoding = { workspace = true }
kdl = { workspace = true }
rmp-serde = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_urlencoded = { workspace = true }
toml = { workspace = true }
quick-xml = { workspace = true, features = ["serialize", "overlapped-lists"] }
unicode-segmentation = { workspace = true }
[profile.release]
lto = true
lto = true

View File

@ -1,20 +1,25 @@
FROM docker.io/alpine:3.18 as build_fe
RUN apk add --no-cache --no-progress git alpine-sdk nodejs-current npm
WORKDIR /fe_calckey
COPY ./fe_calckey/frontend ./frontend
WORKDIR /fe_calckey/frontend
RUN corepack enable && corepack prepare pnpm@latest --activate && pnpm i --frozen-lockfile
RUN env NODE_ENV=production sh -c "pnpm run build && pnpm run gulp"
FROM docker.io/rust:1.71-bullseye as build
FROM docker.io/rust:1.82-bookworm as build
RUN update-ca-certificates
RUN apt update
RUN apt install -y git nodejs npm
RUN npm install -g corepack
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /magnetar
COPY ./ .
WORKDIR /magnetar/fe_calckey/frontend
RUN pnpm i --frozen-lockfile
WORKDIR /magnetar
RUN cargo build --release --locked --workspace --bins
WORKDIR /magnetar/fe_calckey/frontend
RUN env NODE_ENV=production sh -c "pnpm run build && pnpm run gulp"
ENV USER=magnetar
ENV UID=10001
@ -26,13 +31,10 @@ RUN adduser \
--uid "${UID}" \
"${USER}"
WORKDIR /magnetar
FROM docker.io/debian:bookworm-slim
COPY ./ .
RUN cargo build --release --locked --workspace --bins
FROM docker.io/debian:bullseye-slim
RUN apt update
RUN apt install -y openssl
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
@ -42,10 +44,10 @@ COPY --from=build /etc/group /etc/group
WORKDIR /magnetar
WORKDIR /magnetar/fe_calckey/frontend
COPY --from=build_fe /fe_calckey/frontend/built ./built
COPY --from=build_fe /fe_calckey/frontend/assets ./assets
COPY --from=build_fe /fe_calckey/frontend/client/assets ./client/assets
COPY --from=build_fe /fe_calckey/frontend/assets-be ./assets-be
COPY --from=build /magnetar/fe_calckey/frontend/built ./built
COPY --from=build /magnetar/fe_calckey/frontend/assets ./assets
COPY --from=build /magnetar/fe_calckey/frontend/client/assets ./client/assets
COPY --from=build /magnetar/fe_calckey/frontend/assets-be ./assets-be
WORKDIR /magnetar
@ -59,4 +61,4 @@ USER magnetar:magnetar
EXPOSE 4938/tcp
EXPOSE 4939/tcp
ENTRYPOINT ["/magnetar/magnetar"]
ENTRYPOINT ["/magnetar/magnetar"]

3
config/.gitignore vendored
View File

@ -1,3 +1,4 @@
*
!.gitignore
!default.toml
!default.toml
!default-vars.kdl

34
config/default-vars.kdl Normal file
View File

@ -0,0 +1,34 @@
cache {
local-user-cache {
// Size is unlimited
lifetime 300min
}
emoji-cache {
size 4096
}
remote-instance-cache {
size 256
lifetime 100s
}
drive-file-cache {
size 128
lifetime 10s
}
}
api-model {
note {
buffer 10
}
notification {
buffer 10
}
}
activity-pub {
user-agent "magnetar/$version ($host)"
}

View File

@ -47,6 +47,33 @@
# Environment variable: MAG_C_BIND_ADDR
# networking.bind_addr = "::"
# [Optional]
# The URL of a media proxy
# Default: null
# Environment variable: MAG_C_MEDIA_PROXY
# networking.media_proxy = ""
# [Optional]
# Whether to proxy remote files through this instance
# Default: false
# Environment variable: MAG_C_PROXY_REMOTE_FILES
# networking.proxy_remote_files = false
# ------------------------------[ RPC CONNECTION ]-----------------------------
# [Optional]
# A type of connection to use for the application's internal RPC
# Possible values: "none", "tcp", "unix"
# Default: "none"
# Environment variable: MAG_C_RPC_CONNECTION_TYPE
# rpc.connection_type = "none"
# [Optional]
# The corresponding bind address (or path for Unix-domain sockets) for the internal RPC
# Default: ""
# Environment variable: MAG_C_RPC_BIND_ADDR
# rpc.bind_addr = ""
# -----------------------------[ CALCKEY FRONTEND ]----------------------------
# [Optional]
@ -70,7 +97,6 @@
# -------------------------------[ FEDERATION ]--------------------------------
# --------------------------------[ BRANDING ]---------------------------------
# [Optional]

View File

@ -1,5 +1,8 @@
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::borrow::{Borrow, Cow};
use std::{
borrow::{Borrow, Cow},
fmt::Display,
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Acct(String);
@ -23,6 +26,12 @@ impl AsRef<str> for Acct {
}
}
impl Display for Acct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "acct:{}", self.0)
}
}
impl Serialize for Acct {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where

View File

@ -8,10 +8,12 @@ pub mod acct;
pub trait ContentType: Serialize {
fn mime_type(&self) -> &'static str;
fn alt_mime_types(&self) -> &[&'static str];
}
macro_rules! content_type {
($visib:vis $typ:ident, $expression:expr) => {
($visib:vis $typ:ident, $expression:expr $(,$alt:expr)*) => {
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
$visib struct $typ;
@ -31,13 +33,17 @@ macro_rules! content_type {
fn mime_type(&self) -> &'static str {
$expression
}
fn alt_mime_types(&self) -> &[&'static str] {
&[$($alt),*]
}
}
impl<'de> Deserialize<'de> for $typ {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let content_type = String::deserialize(deserializer)?;
if matches!(content_type.as_ref(), $expression) {
if matches!(content_type.as_ref(), $expression $(| $alt)*) {
Ok(Self)
} else {
Err(Error::custom(format!(
@ -54,9 +60,11 @@ pub mod content_type {
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
content_type!(pub ContentActivityStreams, "application/activity+json");
content_type!(pub ContentActivityStreams, "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "application/activity+json", "application/activity+json; charset=utf-8");
content_type!(pub ContentActivityJson, "application/activity+json", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "application/activity+json; charset=utf-8");
content_type!(pub ContentHtml, "text/html");
content_type!(pub ContentJson, "application/json");
content_type!(pub ContentXrdXml, "application/xrd+xml");
content_type!(pub ContentJrdJson, "application/jrd+json");
content_type!(pub ContentMultipartFormData, "multipart/form-data");
content_type!(pub ContentUrlEncoded, "application/x-www-form-urlencoded");
@ -112,6 +120,9 @@ pub mod rel {
link_rel!(pub RelWebFingerProfilePage, "http://webfinger.net/rel/profile-page");
link_rel!(pub RelSelf, "self");
link_rel!(pub RelNext, "next");
link_rel!(pub RelPrev, "prev");
link_rel!(pub RelLrdd, "lrdd");
link_rel!(pub RelOStatusSubscribe, "http://ostatus.org/schema/1.0/subscribe");
link_rel!(pub RelNodeInfo20, "http://nodeinfo.diaspora.software/ns/schema/2.0");
link_rel!(pub RelNodeInfo21, "http://nodeinfo.diaspora.software/ns/schema/2.1");
@ -139,8 +150,7 @@ where
let dt = data
.iter()
.filter_map(Value::as_str)
.filter_map(|val| T::from_str(val).ok())
.next();
.find_map(|val| T::from_str(val).ok());
if let Some(value) = dt {
Ok(ListContaining(value))

View File

@ -1,237 +0,0 @@
pub mod link;
pub mod object;
pub mod recipe;
use crate::link::Link;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::borrow::Cow;
use std::fmt::Debug;
use std::ops::Deref;
use url::Url;
pub trait ObjectSingle: Clone + Debug + Sized + 'static {
fn get_type() -> &'static str;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ObjectRaw<T> {
#[serde(flatten)]
data: Box<T>,
#[serde(flatten)]
raw: Value,
}
impl<T> Deref for ObjectRaw<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T> ObjectRaw<T> {
pub fn into_inner(self) -> Box<T> {
self.data
}
}
impl<T> AsRef<T> for ObjectRaw<T> {
fn as_ref(&self) -> &T {
self.as_data()
}
}
impl<T> ObjectRaw<T> {
pub fn as_data(&self) -> &T {
&self.data
}
pub fn as_json(&self) -> &Value {
&self.raw
}
}
#[async_trait::async_trait]
pub trait Resolver: Send + Sync {
type Error: Send + Sync;
async fn resolve<T: for<'a> Deserialize<'a>>(&self, id: &str) -> Result<T, Self::Error>;
}
#[async_trait::async_trait]
pub trait Resolvable {
type Output: Clone + Send + Sync;
async fn resolve<R: Resolver>(&self, resolver: &R) -> Result<Cow<Self::Output>, R::Error>;
fn unresolve(&self) -> Option<&str>;
}
#[macro_export]
macro_rules! def_ld {
($obj_type: expr, $x: ty) => {
impl $crate::ObjectSingle for $x {
fn get_type() -> &'static str {
$obj_type
}
}
};
}
#[macro_export]
macro_rules! ld_document {
($y: ident, $x: ty) => {
impl $y {
pub async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<ObjectRaw<$x>>, R::Error> {
match self {
$y::Object(obj) => Ok(std::borrow::Cow::Borrowed(obj)),
$y::Remote(Id(link)) => {
Ok(std::borrow::Cow::Owned(resolver.resolve(link).await?))
}
}
}
pub fn unresolve(&self) -> Option<&str> {
match self {
$y::Object(obj) => obj.as_data().as_id.as_deref(),
$y::Remote(Id(id)) => Some(&id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum $y {
Remote(Id),
Object(Box<ObjectRaw<$x>>),
}
#[async_trait::async_trait]
impl $crate::Resolvable for $y {
type Output = $crate::ObjectRaw<$x>;
async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<Self::Output>, R::Error> {
self.resolve(resolver).await
}
fn unresolve(&self) -> Option<&str> {
self.unresolve()
}
}
/*
impl ObjectRaw<$x> {
pub fn try_cast<T>() -> T {}
}
*/
};
}
#[macro_export]
macro_rules! ld_union {
($y: ident, $z: ident, $($item_name: ident as $item_type: ty),+) => {
impl $y {
pub async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<ObjectRaw<$z>>, R::Error> {
match self {
$y::Object(obj) => Ok(std::borrow::Cow::Borrowed(obj)),
$y::Remote(Id(link)) => {
Ok(std::borrow::Cow::Owned(resolver.resolve(link).await?))
}
}
}
pub fn unresolve(&self) -> Option<&str> {
match self {
$y::Object(obj) => obj.as_data().base.as_id.as_deref(),
$y::Remote(Id(id)) => Some(&id),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct $z {
#[serde(flatten)]
pub base: $crate::Base,
$(
#[serde(flatten)]
$item_name: Box<$item_type>,
)+
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum $y {
Remote(Id),
Object(ObjectRaw<$z>),
}
#[async_trait::async_trait]
impl $crate::Resolvable for $y {
type Output = $crate::ObjectRaw<$z>;
async fn resolve<R: $crate::Resolver>(
&self,
resolver: &R,
) -> Result<std::borrow::Cow<Self::Output>, R::Error> {
self.resolve(resolver).await
}
fn unresolve(&self) -> Option<&str> {
self.unresolve()
}
}
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OneOrMore<T> {
One(T),
Many(Vec<T>),
}
impl<T> OneOrMore<T> {
pub fn into_vec(self) -> Vec<T> {
match self {
OneOrMore::One(x) => vec![x],
OneOrMore::Many(x) => x,
}
}
pub fn iter(&self) -> std::slice::Iter<'_, T> {
match self {
OneOrMore::One(x) => std::slice::from_ref(x).iter(),
OneOrMore::Many(x) => x.iter(),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Base {
#[serde(rename = "@type")]
as_type: Option<OneOrMore<String>>,
#[serde(rename = "@id")]
as_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LinkOrUrl {
Url(Url),
Link(Box<Link>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Id(pub String);

View File

@ -1,240 +0,0 @@
use crate::object::{Object, RefObjectLinkUnion};
use crate::{def_ld, ld_document, Id, ObjectRaw, ObjectSingle, OneOrMore};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Activity {
#[serde(rename = "https://www.w3.org/ns/activitystreams#actor")]
#[serde(skip_serializing_if = "Option::is_none")]
pub actor: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#target")]
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#result")]
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#origin")]
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#instrument")]
#[serde(skip_serializing_if = "Option::is_none")]
pub instrument: Option<OneOrMore<RefObjectLinkUnion>>,
}
def_ld!("Activity", Activity, base as Object);
ld_document!(RefActivity, Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct IntransitiveActivity;
def_ld!(
"IntransitiveActivity",
IntransitiveActivity,
base as Activity
);
ld_document!(RefIntransitiveActivity, IntransitiveActivity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAccept;
def_ld!("Accept", ActivityAccept, base as Activity);
ld_document!(RefActivityAccept, ActivityAccept);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeAccept;
def_ld!(
"TentativeAccept",
ActivityTentativeAccept,
base as ActivityAccept
);
ld_document!(RefActivityTentativeAccept, ActivityTentativeAccept);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAdd;
def_ld!("Add", ActivityAdd, base as Activity);
ld_document!(RefActivityAdd, ActivityAdd);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityArrive;
def_ld!("Arrive", ActivityArrive, base as Activity);
ld_document!(RefActivityArrive, ActivityArrive);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityCreate;
def_ld!("Create", ActivityCreate, base as Activity);
ld_document!(RefActivityCreate, ActivityCreate);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDelete;
def_ld!("Delete", ActivityDelete, base as Activity);
ld_document!(RefActivityDelete, ActivityDelete);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFollow;
def_ld!("Follow", ActivityFollow, base as Activity);
ld_document!(RefActivityFollow, ActivityFollow);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityIgnore;
def_ld!("Ignore", ActivityIgnore, base as Activity);
ld_document!(RefActivityIgnore, ActivityIgnore);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityJoin;
def_ld!("Join", ActivityJoin, base as Activity);
ld_document!(RefActivityJoin, ActivityJoin);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLeave;
def_ld!("Leave", ActivityLeave, base as Activity);
ld_document!(RefActivityLeave, ActivityLeave);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLike;
def_ld!("Like", ActivityLike, base as Activity);
ld_document!(RefActivityLike, ActivityLike);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityOffer;
def_ld!("Offer", ActivityOffer, base as Activity);
ld_document!(RefActivityOffer, ActivityOffer);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityInvite;
def_ld!("Invite", ActivityInvite, base as ActivityOffer);
ld_document!(RefActivityInvite, ActivityInvite);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityReject;
def_ld!("Reject", ActivityReject, base as Activity);
ld_document!(RefActivityReject, ActivityReject);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeReject;
def_ld!(
"TentativeReject",
ActivityTentativeReject,
base as ActivityReject
);
ld_document!(RefActivityTentativeReject, ActivityTentativeReject);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRemove;
def_ld!("Remove", ActivityRemove, base as Activity);
ld_document!(RefActivityRemove, ActivityRemove);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUndo;
def_ld!("Undo", ActivityUndo, base as Activity);
ld_document!(RefActivityUndo, ActivityUndo);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUpdate;
def_ld!("Update", ActivityUpdate, base as Activity);
ld_document!(RefActivityUpdate, ActivityUpdate);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityView;
def_ld!("View", ActivityView, base as Activity);
ld_document!(RefActivityView, ActivityView);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityListen;
def_ld!("Listen", ActivityListen, base as Activity);
ld_document!(RefActivityListen, ActivityListen);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRead;
def_ld!("Read", ActivityRead, base as Activity);
ld_document!(RefActivityRead, ActivityRead);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityMove;
def_ld!("Move", ActivityMove, base as Activity);
ld_document!(RefActivityMove, ActivityMove);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTravel;
def_ld!("Travel", ActivityTravel, base as IntransitiveActivity);
ld_document!(RefActivityTravel, ActivityTravel);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAnnounce;
def_ld!("Announce", ActivityAnnounce, base as Activity);
ld_document!(RefActivityAnnounce, ActivityAnnounce);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityBlock;
def_ld!("Block", ActivityBlock, base as ActivityIgnore);
ld_document!(RefActivityBlock, ActivityBlock);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFlag;
def_ld!("Flag", ActivityFlag, base as Activity);
ld_document!(RefActivityFlag, ActivityFlag);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDislike;
def_ld!("Dislike", ActivityDislike, base as Activity);
ld_document!(RefActivityDislike, ActivityDislike);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ActivityClosedStatus {
Date(DateTime<Utc>),
Bool(bool),
Other(RefObjectLinkUnion),
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityQuestion {
#[serde(rename = "https://www.w3.org/ns/activitystreams#oneOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub one_of: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#anyOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<OneOrMore<RefObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#closed")]
#[serde(skip_serializing_if = "Option::is_none")]
pub closed: Option<OneOrMore<ActivityClosedStatus>>,
}
def_ld!("Question", ActivityQuestion, base as IntransitiveActivity);
ld_document!(RefActivityQuestion, ActivityQuestion);

View File

@ -1,5 +1,5 @@
[package]
name = "magnetar_activity_pub"
name = "magnetar_activity_streams"
version.workspace = true
edition.workspace = true
license = "MIT OR Apache-2.0"
@ -9,11 +9,14 @@ crate-type = ["rlib"]
[dependencies]
async-trait = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
either = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }
url = { workspace = true, features = ["serde"] }
[dev-dependencies]
tokio = { workspace = true, features = ["full"] }
tokio = { workspace = true, features = ["full"] }

View File

@ -0,0 +1,88 @@
use std::borrow::Cow;
use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::link::Link;
pub mod link;
pub mod object;
pub mod recipe;
pub trait ObjectTyped: Clone + Debug + Sized + 'static {
fn get_type() -> &'static str;
}
#[macro_export]
macro_rules! def_ld {
($obj_type: expr, $x: ty) => {
impl $crate::ObjectTyped for $x {
fn get_type() -> &'static str {
$obj_type
}
}
};
}
#[macro_export]
macro_rules! ld_union {
($z: ident, $($item_name: ident as $item_type: ty),+) => {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct $z {
$(
#[serde(flatten)]
$item_name: Box<$item_type>,
)+
}
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OneOrMore<T> {
One(T),
Many(Vec<T>),
}
impl<T> OneOrMore<T> {
pub fn into_vec(self) -> Vec<T> {
match self {
OneOrMore::One(x) => vec![x],
OneOrMore::Many(x) => x,
}
}
pub fn iter(&self) -> std::slice::Iter<'_, T> {
match self {
OneOrMore::One(x) => std::slice::from_ref(x).iter(),
OneOrMore::Many(x) => x.iter(),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Base {
#[serde(rename = "@type")]
as_type: Option<OneOrMore<String>>,
#[serde(rename = "@id")]
as_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LinkOrUrl<'a> {
Url(Url),
Link(Box<Link<'a>>),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[repr(transparent)]
pub struct Id(pub String);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ObjectSlot<'a, T: ObjectTyped> {
Id(Id),
Value(Cow<'a, T>),
}

View File

@ -1,8 +1,7 @@
use crate::{def_ld, ld_document, Id, ObjectRaw};
use crate::def_ld;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct LinkMention;
pub struct LinkMention {}
def_ld!("https://www.w3.org/ns/activitystreams#Mention", LinkMention);
ld_document!(RefLinkMention, LinkMention);

View File

@ -1,12 +1,13 @@
use crate::object::RefObjectLinkUnion;
use crate::OneOrMore;
use crate::{def_ld, ld_document, Id, ObjectRaw};
use serde::{Deserialize, Serialize};
use crate::{def_ld, ObjectSlot};
use crate::object::ObjectLinkUnion;
use crate::OneOrMore;
pub mod link_types;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Link {
pub struct Link<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#href")]
#[serde(skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
@ -37,8 +38,7 @@ pub struct Link {
#[serde(rename = "https://www.w3.org/ns/activitystreams#preview")]
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<OneOrMore<RefObjectLinkUnion>>,
pub preview: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Link", Link);
ld_document!(RefLink, Link);
def_ld!("https://www.w3.org/ns/activitystreams#Link", Link<'_>);

View File

@ -0,0 +1,211 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{def_ld, ObjectSlot, ObjectTyped, OneOrMore};
use crate::object::ObjectLinkUnion;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Activity<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#actor")]
#[serde(skip_serializing_if = "Option::is_none")]
pub actor: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#target")]
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#result")]
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#origin")]
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#instrument")]
#[serde(skip_serializing_if = "Option::is_none")]
pub instrument: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Activity", Activity<'_>, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct IntransitiveActivity {}
def_ld!(
"https://www.w3.org/ns/activitystreams#IntransitiveActivity",
IntransitiveActivity,
base as Activity
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAccept {}
def_ld!("https://www.w3.org/ns/activitystreams#Accept", ActivityAccept, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeAccept {}
def_ld!(
"https://www.w3.org/ns/activitystreams#TentativeAccept",
ActivityTentativeAccept,
base as ActivityAccept
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAdd;
def_ld!("https://www.w3.org/ns/activitystreams#Add", ActivityAdd, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityArrive;
def_ld!("https://www.w3.org/ns/activitystreams#Arrive", ActivityArrive, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityCreate;
def_ld!("https://www.w3.org/ns/activitystreams#Create", ActivityCreate, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDelete {}
def_ld!("https://www.w3.org/ns/activitystreams#Delete", ActivityDelete, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFollow {}
def_ld!("https://www.w3.org/ns/activitystreams#Follow", ActivityFollow, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityIgnore {}
def_ld!("https://www.w3.org/ns/activitystreams#Ignore", ActivityIgnore, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityJoin {}
def_ld!("https://www.w3.org/ns/activitystreams#Join", ActivityJoin, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLeave {}
def_ld!("https://www.w3.org/ns/activitystreams#Leave", ActivityLeave, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityLike {}
def_ld!("https://www.w3.org/ns/activitystreams#Like", ActivityLike, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityOffer {}
def_ld!("https://www.w3.org/ns/activitystreams#Offer", ActivityOffer, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityInvite {}
def_ld!("https://www.w3.org/ns/activitystreams#Invite", ActivityInvite, base as ActivityOffer);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityReject {}
def_ld!("https://www.w3.org/ns/activitystreams#Reject", ActivityReject, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTentativeReject {}
def_ld!(
"https://www.w3.org/ns/activitystreams#TentativeReject",
ActivityTentativeReject,
base as ActivityReject
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRemove {}
def_ld!("https://www.w3.org/ns/activitystreams#Remove", ActivityRemove, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUndo {}
def_ld!("https://www.w3.org/ns/activitystreams#Undo", ActivityUndo, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityUpdate {}
def_ld!("https://www.w3.org/ns/activitystreams#Update", ActivityUpdate, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityView {}
def_ld!("https://www.w3.org/ns/activitystreams#View", ActivityView, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityListen {}
def_ld!("https://www.w3.org/ns/activitystreams#Listen", ActivityListen, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityRead {}
def_ld!("https://www.w3.org/ns/activitystreams#Read", ActivityRead, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityMove {}
def_ld!("https://www.w3.org/ns/activitystreams#Move", ActivityMove, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityTravel {}
def_ld!("https://www.w3.org/ns/activitystreams#Travel", ActivityTravel, base as IntransitiveActivity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityAnnounce {}
def_ld!("https://www.w3.org/ns/activitystreams#Announce", ActivityAnnounce, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityBlock {}
def_ld!("https://www.w3.org/ns/activitystreams#Block", ActivityBlock, base as ActivityIgnore);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityFlag {}
def_ld!("https://www.w3.org/ns/activitystreams#Flag", ActivityFlag, base as Activity);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityDislike {}
def_ld!("https://www.w3.org/ns/activitystreams#Dislike", ActivityDislike, base as Activity);
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ActivityClosedStatus<'a> {
Date(DateTime<Utc>),
Bool(bool),
Other(ObjectSlot<'a, ObjectLinkUnion>),
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActivityQuestion<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#oneOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub one_of: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#anyOf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub any_of: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#closed")]
#[serde(skip_serializing_if = "Option::is_none")]
pub closed: Option<OneOrMore<ActivityClosedStatus<'a>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Question", ActivityQuestion<'_>, base as IntransitiveActivity);

View File

@ -1,32 +1,34 @@
use crate::object::{RefCollection, RefOrderedCollection};
use crate::{def_ld, ld_document, Id, ObjectRaw};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{def_ld, ObjectSlot};
use crate::object::{Collection, ObjectLinkUnion, OrderedCollection};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorActivityPubProps {
pub struct ActorActivityPubProps<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#inbox")]
#[serde(skip_serializing_if = "Option::is_none")]
pub inbox: Option<RefOrderedCollection>,
pub inbox: Option<ObjectSlot<'a, OrderedCollection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#outbox")]
#[serde(skip_serializing_if = "Option::is_none")]
pub outbox: Option<RefOrderedCollection>,
pub outbox: Option<ObjectSlot<'a, OrderedCollection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#following")]
#[serde(skip_serializing_if = "Option::is_none")]
pub following: Option<RefCollection>,
pub following: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#followers")]
#[serde(skip_serializing_if = "Option::is_none")]
pub followers: Option<RefCollection>,
pub followers: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#liked")]
#[serde(skip_serializing_if = "Option::is_none")]
pub liked: Option<RefCollection>,
pub liked: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#shares")]
#[serde(skip_serializing_if = "Option::is_none")]
pub shares: Option<RefCollection>,
pub shares: Option<ObjectSlot<'a, Collection>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#preferredUsername")]
#[serde(skip_serializing_if = "Option::is_none")]
@ -34,35 +36,30 @@ pub struct ActorActivityPubProps {
#[serde(rename = "https://www.w3.org/ns/activitystreams#endpoints")]
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoints: Option<String>,
pub endpoints: Option<Value>,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorApplication;
pub struct ActorApplication {}
def_ld!("Application", ActorApplication, base as Object);
ld_document!(RefActorApplication, ActorApplication);
def_ld!("https://www.w3.org/ns/activitystreams#Application", ActorApplication, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorGroup;
pub struct ActorGroup {}
def_ld!("Group", ActorGroup, base as Object);
ld_document!(RefActorGroup, ActorGroup);
def_ld!("https://www.w3.org/ns/activitystreams#Group", ActorGroup, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorOrganization;
pub struct ActorOrganization {}
def_ld!("Organization", ActorOrganization, base as Object);
ld_document!(RefActorOrganization, ActorOrganization);
def_ld!("https://www.w3.org/ns/activitystreams#Organization", ActorOrganization, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorPerson;
pub struct ActorPerson {}
def_ld!("Person", ActorPerson, base as Object);
ld_document!(RefActorPerson, ActorPerson);
def_ld!("https://www.w3.org/ns/activitystreams#Person", ActorPerson, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ActorService;
pub struct ActorService {}
def_ld!("Service", ActorService, base as Object);
ld_document!(RefActorService, ActorService);
def_ld!("https://www.w3.org/ns/activitystreams#Service", ActorService, base as Object);

View File

@ -1,28 +1,30 @@
use crate::link::Link;
use crate::object::object_types::RefObjectImageLinkUnion;
use crate::{def_ld, Id};
use crate::{ld_document, ld_union, Base, LinkOrUrl, ObjectRaw, ObjectSingle, OneOrMore};
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::{ld_union, LinkOrUrl, ObjectSlot, OneOrMore};
use crate::def_ld;
use crate::link::Link;
use crate::object::object_types::ObjectImageLinkUnion;
pub mod activity;
pub mod actor;
pub mod object_types;
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct Object {
pub struct Object<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#attachment")]
#[serde(skip_serializing_if = "Option::is_none")]
pub attachment: Option<OneOrMore<RefObjectLinkUnion>>,
pub attachment: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#attributedTo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub attributed_to: Option<OneOrMore<RefObjectLinkUnion>>,
pub attributed_to: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#audience")]
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<OneOrMore<RefObjectLinkUnion>>,
pub audience: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#content")]
#[serde(skip_serializing_if = "Option::is_none")]
@ -34,7 +36,7 @@ pub struct Object {
#[serde(rename = "https://www.w3.org/ns/activitystreams#context")]
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<OneOrMore<RefObjectLinkUnion>>,
pub context: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#name")]
#[serde(skip_serializing_if = "Option::is_none")]
@ -50,27 +52,27 @@ pub struct Object {
#[serde(rename = "https://www.w3.org/ns/activitystreams#generator")]
#[serde(skip_serializing_if = "Option::is_none")]
pub generator: Option<OneOrMore<RefObjectLinkUnion>>,
pub generator: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#icon")]
#[serde(skip_serializing_if = "Option::is_none")]
pub icon: Option<OneOrMore<RefObjectImageLinkUnion>>,
pub icon: Option<OneOrMore<ObjectSlot<'a, ObjectImageLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#image")]
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<OneOrMore<RefObjectImageLinkUnion>>,
pub image: Option<OneOrMore<ObjectSlot<'a, ObjectImageLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#inReplyTo")]
#[serde(skip_serializing_if = "Option::is_none")]
pub in_reply_to: Option<OneOrMore<RefObjectLinkUnion>>,
pub in_reply_to: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#location")]
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<OneOrMore<RefObjectLinkUnion>>,
pub location: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#preview")]
#[serde(skip_serializing_if = "Option::is_none")]
pub preview: Option<OneOrMore<RefObjectLinkUnion>>,
pub preview: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#published")]
#[serde(skip_serializing_if = "Option::is_none")]
@ -78,7 +80,7 @@ pub struct Object {
#[serde(rename = "https://www.w3.org/ns/activitystreams#replies")]
#[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<RefCollectionLinkUnion>,
pub replies: Option<ObjectSlot<'a, CollectionLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#startTime")]
#[serde(skip_serializing_if = "Option::is_none")]
@ -94,7 +96,7 @@ pub struct Object {
#[serde(rename = "https://www.w3.org/ns/activitystreams#tag")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<OneOrMore<RefObjectLinkUnion>>,
pub tag: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#updated")]
#[serde(skip_serializing_if = "Option::is_none")]
@ -102,23 +104,23 @@ pub struct Object {
#[serde(rename = "https://www.w3.org/ns/activitystreams#url")]
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<OneOrMore<LinkOrUrl>>,
pub url: Option<OneOrMore<LinkOrUrl<'a>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#to")]
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<OneOrMore<RefObjectLinkUnion>>,
pub to: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#bto")]
#[serde(skip_serializing_if = "Option::is_none")]
pub bto: Option<OneOrMore<RefObjectLinkUnion>>,
pub bto: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#cc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub cc: Option<OneOrMore<RefObjectLinkUnion>>,
pub cc: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#bcc")]
#[serde(skip_serializing_if = "Option::is_none")]
pub bcc: Option<OneOrMore<RefObjectLinkUnion>>,
pub bcc: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#mediaType")]
#[serde(skip_serializing_if = "Option::is_none")]
@ -131,13 +133,11 @@ pub struct Object {
// ActivityPub
#[serde(rename = "https://www.w3.org/ns/activitystreams#source")]
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<RefObject>,
pub source: Option<ObjectSlot<'a, Object<'a>>>,
}
def_ld!("https://www.w3.org/ns/activitystreams#Object", Object);
ld_document!(RefObject, Object);
def_ld!("https://www.w3.org/ns/activitystreams#Object", Object<'_>);
ld_union!(
RefObjectLinkUnion,
ObjectLinkUnion,
object_props as Object,
link_props as Link
@ -170,9 +170,7 @@ def_ld!(
"https://www.w3.org/ns/activitystreams#Collection",
Collection
);
ld_document!(RefCollection, Collection);
ld_union!(
RefCollectionLinkUnion,
CollectionLinkUnion,
collection_props as Collection,
link_props as Link
@ -189,7 +187,6 @@ def_ld!(
"https://www.w3.org/ns/activitystreams#OrderedCollection",
OrderedCollection
);
ld_document!(RefOrderedCollection, OrderedCollection);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct CollectionPage {
@ -210,10 +207,7 @@ def_ld!(
"https://www.w3.org/ns/activitystreams#CollectionPage",
CollectionPage
);
ld_document!(RefCollectionPage, CollectionPage);
ld_union!(
RefCollectionPageLinkUnion,
CollectionPageLinkUnion,
page_props as CollectionPage,
link_props as Link
@ -230,4 +224,3 @@ def_ld!(
"https://www.w3.org/ns/activitystreams#OrderedCollectionPage",
OrderedCollectionPage
);
ld_document!(RefOrderedCollectionPage, OrderedCollectionPage);

View File

@ -1,80 +1,71 @@
use crate::link::Link;
use crate::object::{Object, RefObject, RefObjectLinkUnion};
use crate::{def_ld, ld_document, ld_union, Id, ObjectRaw, ObjectSingle, OneOrMore};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::{def_ld, ld_union, ObjectSlot, ObjectTyped, OneOrMore};
use crate::link::Link;
use crate::object::{Object, ObjectLinkUnion};
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectRelationship {
pub struct ObjectRelationship<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#object")]
#[serde(skip_serializing_if = "Option::is_none")]
pub object: Option<OneOrMore<RefObjectLinkUnion>>,
pub object: Option<OneOrMore<ObjectSlot<'a, ObjectLinkUnion>>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#subject")]
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<RefObjectLinkUnion>,
pub subject: Option<ObjectSlot<'a, ObjectLinkUnion>>,
#[serde(rename = "https://www.w3.org/ns/activitystreams#relationship")]
#[serde(skip_serializing_if = "Option::is_none")]
pub relationship: Option<OneOrMore<RefObject>>,
pub relationship: Option<OneOrMore<ObjectSlot<'a, Object<'a>>>>,
}
def_ld!("Relationship", ObjectRelationship, base as Object);
ld_document!(RefObjectRelationship, ObjectRelationship);
def_ld!("https://www.w3.org/ns/activitystreams#Relationship", ObjectRelationship, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectArticle;
pub struct ObjectArticle {}
def_ld!("Article", ObjectArticle, base as Object);
ld_document!(RefObjectArticle, ObjectArticle);
def_ld!("https://www.w3.org/ns/activitystreams#Article", ObjectArticle, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectDocument;
pub struct ObjectDocument {}
def_ld!("Document", ObjectDocument, base as Object);
ld_document!(RefObjectDocument, ObjectDocument);
def_ld!("https://www.w3.org/ns/activitystreams#Document", ObjectDocument, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectAudio;
pub struct ObjectAudio {}
def_ld!("Audio", ObjectAudio, base as ObjectDocument);
ld_document!(RefObjectAudio, ObjectAudio);
def_ld!("https://www.w3.org/ns/activitystreams#Audio", ObjectAudio, base as ObjectDocument);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectImage;
pub struct ObjectImage {}
def_ld!("Image", ObjectImage, base as ObjectDocument);
ld_document!(RefObjectImage, ObjectImage);
def_ld!("https://www.w3.org/ns/activitystreams#Image", ObjectImage, base as ObjectDocument);
ld_union!(
RefObjectImageLinkUnion,
ObjectImageLinkUnion,
image_props as ObjectImage,
link_props as Link
);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectVideo;
pub struct ObjectVideo {}
def_ld!("Video", ObjectVideo, base as ObjectDocument);
ld_document!(RefObjectVideo, ObjectVideo);
def_ld!("https://www.w3.org/ns/activitystreams#Video", ObjectVideo, base as ObjectDocument);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectNote;
pub struct ObjectNote {}
def_ld!("Note", ObjectNote, base as Object);
ld_document!(RefObjectNote, ObjectNote);
def_ld!("https://www.w3.org/ns/activitystreams#Note", ObjectNote, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectPage;
pub struct ObjectPage {}
def_ld!("Page", ObjectPage, base as ObjectDocument);
ld_document!(RefObjectPage, ObjectPage);
def_ld!("https://www.w3.org/ns/activitystreams#Page", ObjectPage, base as ObjectDocument);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectEvent;
pub struct ObjectEvent {}
def_ld!("Event", ObjectEvent, base as Object);
ld_document!(RefObjectEvent, ObjectEvent);
def_ld!("https://www.w3.org/ns/activitystreams#Event", ObjectEvent, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectPlace {
@ -103,18 +94,16 @@ pub struct ObjectPlace {
pub units: Option<String>,
}
def_ld!("Place", ObjectPlace, base as Object);
ld_document!(RefObjectPlace, ObjectPlace);
def_ld!("https://www.w3.org/ns/activitystreams#Place", ObjectPlace, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectProfile {
pub struct ObjectProfile<'a> {
#[serde(rename = "https://www.w3.org/ns/activitystreams#describes")]
#[serde(skip_serializing_if = "Option::is_none")]
pub describes: Option<RefObject>,
pub describes: Option<ObjectSlot<'a, ObjectProfile<'a>>>,
}
def_ld!("Profile", ObjectProfile, base as Object);
ld_document!(RefObjectProfile, ObjectProfile);
def_ld!("https://www.w3.org/ns/activitystreams#Profile", ObjectProfile, base as Object);
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct ObjectTombstone {
@ -127,5 +116,4 @@ pub struct ObjectTombstone {
pub deleted: Option<DateTime<Utc>>,
}
def_ld!("Tombstone", ObjectTombstone, base as Object);
ld_document!(RefObjectTombstone, ObjectTombstone);
def_ld!("https://www.w3.org/ns/activitystreams#Tombstone", ObjectTombstone, base as Object);

View File

@ -1,16 +1,9 @@
use crate::{OneOrMore, Resolvable};
use thiserror::Error;
#[macro_use]
pub mod account_move;
use crate::OneOrMore;
#[macro_use]
pub mod block;
#[macro_use]
pub mod flag;
#[macro_use]
pub mod poll;
#[macro_use]
pub mod tag;
#[derive(Debug, Error)]
pub enum RecipeError {

View File

@ -1,51 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "gallery_like")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "postId")]
pub post_id: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::gallery_post::Entity",
from = "Column::PostId",
to = "super::gallery_post::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
GalleryPost,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
}
impl Related<super::gallery_post::Entity> for Entity {
fn to() -> RelationDef {
Relation::GalleryPost.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,54 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "gallery_post")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "updatedAt")]
pub updated_at: DateTimeWithTimeZone,
pub title: String,
pub description: Option<String>,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "fileIds")]
pub file_ids: Vec<String>,
#[sea_orm(column_name = "isSensitive")]
pub is_sensitive: bool,
#[sea_orm(column_name = "likedCount")]
pub liked_count: i32,
pub tags: Vec<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::gallery_like::Entity")]
GalleryLike,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
}
impl Related<super::gallery_like::Entity> for Entity {
fn to() -> RelationDef {
Relation::GalleryLike.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,90 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use super::sea_orm_active_enums::PageVisibilityEnum;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "page")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "updatedAt")]
pub updated_at: DateTimeWithTimeZone,
pub title: String,
pub name: String,
pub summary: Option<String>,
#[sea_orm(column_name = "alignCenter")]
pub align_center: bool,
pub font: String,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "eyeCatchingImageId")]
pub eye_catching_image_id: Option<String>,
#[sea_orm(column_type = "JsonBinary")]
pub content: Json,
#[sea_orm(column_type = "JsonBinary")]
pub variables: Json,
pub visibility: PageVisibilityEnum,
#[sea_orm(column_name = "visibleUserIds")]
pub visible_user_ids: Vec<String>,
#[sea_orm(column_name = "likedCount")]
pub liked_count: i32,
#[sea_orm(column_name = "hideTitleWhenPinned")]
pub hide_title_when_pinned: bool,
pub script: String,
#[sea_orm(column_name = "isPublic")]
pub is_public: bool,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::drive_file::Entity",
from = "Column::EyeCatchingImageId",
to = "super::drive_file::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
DriveFile,
#[sea_orm(has_many = "super::page_like::Entity")]
PageLike,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
#[sea_orm(has_one = "super::user_profile::Entity")]
UserProfile,
}
impl Related<super::drive_file::Entity> for Entity {
fn to() -> RelationDef {
Relation::DriveFile.def()
}
}
impl Related<super::page_like::Entity> for Entity {
fn to() -> RelationDef {
Relation::PageLike.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::user_profile::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserProfile.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,51 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "page_like")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "pageId")]
pub page_id: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::page::Entity",
from = "Column::PageId",
to = "super::page::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
Page,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
}
impl Related<super::page::Entity> for Entity {
fn to() -> RelationDef {
Relation::Page.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,62 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user_group")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
pub name: String,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "isPrivate")]
pub is_private: bool,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
#[sea_orm(has_many = "super::user_group_invitation::Entity")]
UserGroupInvitation,
#[sea_orm(has_many = "super::user_group_invite::Entity")]
UserGroupInvite,
#[sea_orm(has_many = "super::user_group_joining::Entity")]
UserGroupJoining,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::user_group_invitation::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupInvitation.def()
}
}
impl Related<super::user_group_invite::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupInvite.def()
}
}
impl Related<super::user_group_joining::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupJoining.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,59 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user_group_invitation")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "userGroupId")]
pub user_group_id: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::notification::Entity")]
Notification,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
#[sea_orm(
belongs_to = "super::user_group::Entity",
from = "Column::UserGroupId",
to = "super::user_group::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
UserGroup,
}
impl Related<super::notification::Entity> for Entity {
fn to() -> RelationDef {
Relation::Notification.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::user_group::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroup.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,51 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user_group_invite")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "userGroupId")]
pub user_group_id: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
#[sea_orm(
belongs_to = "super::user_group::Entity",
from = "Column::UserGroupId",
to = "super::user_group::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
UserGroup,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::user_group::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroup.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,59 +0,0 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "user_group_joining")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
#[sea_orm(column_name = "createdAt")]
pub created_at: DateTimeWithTimeZone,
#[sea_orm(column_name = "userId")]
pub user_id: String,
#[sea_orm(column_name = "userGroupId")]
pub user_group_id: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::antenna::Entity")]
Antenna,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
User,
#[sea_orm(
belongs_to = "super::user_group::Entity",
from = "Column::UserGroupId",
to = "super::user_group::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
UserGroup,
}
impl Related<super::antenna::Entity> for Entity {
fn to() -> RelationDef {
Relation::Antenna.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::user_group::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroup.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,20 +0,0 @@
pub use sea_orm_migration::prelude::*;
mod m20220101_000001_bootstrap;
mod m20230729_201733_drop_messaging_integrations;
mod m20230729_212237_user_unique_idx;
mod m20230806_142918_drop_featured_note_option;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220101_000001_bootstrap::Migration),
Box::new(m20230729_201733_drop_messaging_integrations::Migration),
Box::new(m20230729_212237_user_unique_idx::Migration),
Box::new(m20230806_142918_drop_featured_note_option::Migration),
]
}
}

57
ext_federation/Cargo.toml Normal file
View File

@ -0,0 +1,57 @@
[package]
name = "magnetar_federation"
version.workspace = true
edition.workspace = true
license = "MIT OR Apache-2.0"
[lib]
crate-type = ["rlib"]
[dependencies]
magnetar_core = { path = "../core" }
magnetar_common = { path = "../magnetar_common" }
magnetar_host_meta = { path = "../ext_host_meta" }
magnetar_webfinger = { path = "../ext_webfinger" }
async-trait = { workspace = true }
async-stream = { workspace = true }
futures = { workspace = true }
futures-core = { workspace = true }
futures-util = { workspace = true }
quick-xml = { workspace = true, features = ["serialize"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
base64 = { workspace = true }
url = { workspace = true, features = ["serde"] }
chrono = { workspace = true, features = ["serde"] }
httpdate = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }
strum = { workspace = true, features = ["derive"] }
http = { workspace = true }
headers = { workspace = true }
hyper = { workspace = true, features = ["full"] }
percent-encoding = { workspace = true }
reqwest = { workspace = true, features = ["stream", "hickory-dns"] }
ed25519-dalek = { workspace = true, features = [
"pem",
"pkcs8",
"signature",
"digest",
] }
rsa = { workspace = true, features = ["sha2"] }
sha2 = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
[dev-dependencies]
tracing-subscriber = { workspace = true }
miette = { workspace = true, features = ["fancy"] }

View File

@ -0,0 +1,482 @@
use chrono::Utc;
use futures::TryFutureExt;
use http::{HeaderMap, HeaderName, HeaderValue, Method};
use indexmap::IndexSet;
use serde_json::Value;
use sha2::Digest;
use std::{fmt::Display, string::FromUtf8Error, sync::Arc};
use thiserror::Error;
use tokio::task;
use tokio::task::JoinError;
use url::Url;
use magnetar_core::web_model::content_type::ContentActivityStreams;
use crate::{
client::federation_client::{FederationClient, FederationClientError},
crypto::{ApSigningError, ApSigningKey, SigningAlgorithm},
ApClientService, ApSignature, ApSigningField, ApSigningHeaders, SigningInput, SigningParts,
};
pub struct ApClientServiceDefaultProvider {
client: Arc<dyn AsRef<FederationClient> + Send + Sync>,
}
impl ApClientServiceDefaultProvider {
pub fn new(client: impl AsRef<FederationClient> + Send + Sync + 'static) -> Self {
Self {
client: Arc::new(client),
}
}
}
impl Display for ApSignature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "keyId=\"{}\"", self.key_id)?;
if let Some(ref algorithm) = self.algorithm {
write!(f, ",algorithm=\"{}\"", algorithm)?;
}
if let Some(ref created) = self.created {
write!(f, ",created={}", created.timestamp())?;
}
if let Some(ref expires) = self.expires {
write!(f, ",expires={}", expires.timestamp())?;
}
if let Some(ref headers) = self.headers {
write!(f, ",headers=\"{}\"", headers)?;
}
write!(f, ",signature=\"{}\"", self.signature)?;
Ok(())
}
}
impl Display for ApSigningHeaders {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(AsRef::as_ref)
.collect::<Vec<_>>()
.join(" ")
)
}
}
#[derive(Debug, Error)]
pub enum ApClientError {
#[error("Federation client error: {0}")]
FederationClientError(#[from] FederationClientError),
#[error("Signing error: {0}")]
SigningError(#[from] ApSigningError),
#[error("URL parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("Failed to serialize JSON: {0}")]
SerializerError(#[from] serde_json::Error),
#[error("Invalid header value: {0}")]
InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
#[error("UTF-8 parse error: {0}")]
Utf8ParseError(#[from] FromUtf8Error),
#[error("Task join error: {0}")]
JoinError(#[from] JoinError),
}
trait CreateField {
fn create_field(&self) -> Option<(ApSigningField, String)>;
}
#[derive(Debug)]
struct RequestTarget<'a> {
url: &'a Url,
method: Method,
}
impl CreateField for RequestTarget<'_> {
fn create_field(&self) -> Option<(ApSigningField, String)> {
Some((
ApSigningField::PseudoRequestTarget,
format!(
"{} {}",
self.method.as_str().to_lowercase(),
self.url.path()
),
))
}
}
#[derive(Debug)]
struct HostPseudoHeader<'a>(&'a Url);
impl CreateField for HostPseudoHeader<'_> {
fn create_field(&self) -> Option<(ApSigningField, String)> {
Some((ApSigningField::Host, self.0.host()?.to_string()))
}
}
#[derive(Debug)]
struct DateHeader(chrono::DateTime<Utc>);
impl CreateField for DateHeader {
fn create_field(&self) -> Option<(ApSigningField, String)> {
Some((ApSigningField::Date, httpdate::fmt_http_date(self.0.into())))
}
}
#[derive(Debug)]
struct CreatedPseudoHeader(chrono::DateTime<Utc>);
impl CreateField for CreatedPseudoHeader {
fn create_field(&self) -> Option<(ApSigningField, String)> {
Some((
ApSigningField::PseudoCreated,
self.0.timestamp().to_string(),
))
}
}
#[derive(Debug)]
struct ExpiresPseudoHeader(chrono::DateTime<Utc>);
impl CreateField for ExpiresPseudoHeader {
fn create_field(&self) -> Option<(ApSigningField, String)> {
Some((
ApSigningField::PseudoExpires,
self.0.timestamp().to_string(),
))
}
}
#[derive(Debug)]
struct DigestHeader<'a>(&'a str);
impl CreateField for DigestHeader<'_> {
fn create_field(&self) -> Option<(ApSigningField, String)> {
Some((ApSigningField::Digest, self.0.to_owned()))
}
}
impl<T: CreateField> CreateField for Option<T> {
fn create_field(&self) -> Option<(ApSigningField, String)> {
self.as_ref().and_then(T::create_field)
}
}
macro_rules! signing_input {
($name:ident, $($field_name:ident => $field_type:path),+ ) => {
#[derive(Debug)]
struct $name<'a> {
$($field_name: $field_type),+
}
impl SigningInput for $name<'_> {
fn create_signing_input(&self) -> Vec<(ApSigningField, String)> {
[$(self.$field_name.create_field()),+].into_iter().flatten().collect::<Vec<_>>()
}
}
}
}
signing_input!(SigningInputGetRsaSha256,
request_target => RequestTarget<'a>,
host => HostPseudoHeader<'a>,
date => DateHeader,
expires => Option<ExpiresPseudoHeader>);
impl SigningParts for SigningInputGetRsaSha256<'_> {
fn get_created(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
None
}
fn get_expires(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
self.expires.as_ref().map(|ExpiresPseudoHeader(v)| v)
}
}
signing_input!(SigningInputGetHs2019,
request_target => RequestTarget<'a>,
host => HostPseudoHeader<'a>,
created => CreatedPseudoHeader,
expires => Option<ExpiresPseudoHeader>);
impl SigningParts for SigningInputGetHs2019<'_> {
fn get_created(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
Some(&self.created.0)
}
fn get_expires(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
self.expires.as_ref().map(|ExpiresPseudoHeader(v)| v)
}
}
signing_input!(SigningInputPostRsaSha256,
request_target => RequestTarget<'a>,
host => HostPseudoHeader<'a>,
date => DateHeader,
digest => DigestHeader<'a>,
expires => Option<ExpiresPseudoHeader>);
impl SigningParts for SigningInputPostRsaSha256<'_> {
fn get_created(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
None
}
fn get_expires(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
self.expires.as_ref().map(|ExpiresPseudoHeader(v)| v)
}
}
signing_input!(SigningInputPostHs2019,
request_target => RequestTarget<'a>,
host => HostPseudoHeader<'a>,
created => CreatedPseudoHeader,
digest => DigestHeader<'a>,
expires => Option<ExpiresPseudoHeader>);
impl SigningParts for SigningInputPostHs2019<'_> {
fn get_created(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
Some(&self.created.0)
}
fn get_expires(&self) -> Option<&chrono::prelude::DateTime<Utc>> {
self.expires.as_ref().map(|ExpiresPseudoHeader(v)| v)
}
}
#[async_trait::async_trait]
impl ApClientService for ApClientServiceDefaultProvider {
type Error = ApClientError;
async fn sign_request(
&self,
signing_key: ApSigningKey<'_>,
signing_algorithm: SigningAlgorithm,
request: &dyn SigningInput,
) -> Result<ApSignature, Self::Error> {
let components = request.create_signing_input();
let message = components
.iter()
.map(|(k, v)| format!("{}: {}", k.as_ref(), v))
.collect::<Vec<_>>()
.join("\n");
let key_id = signing_key.key_id.clone().into_owned();
let key = signing_key.into_owned();
let signature = task::spawn_blocking(move || {
key
.key
.sign_base64(signing_algorithm, &message.into_bytes())
}).await??;
Ok(ApSignature {
key_id,
algorithm: Some(signing_algorithm),
created: request.get_created().cloned(),
expires: request.get_expires().cloned(),
headers: Some(ApSigningHeaders(IndexSet::from_iter(
components.iter().map(|(k, _)| k).copied(),
))),
signature,
})
}
async fn signed_get(
&self,
signing_key: ApSigningKey<'_>,
signing_algorithm: SigningAlgorithm,
expires: Option<chrono::DateTime<Utc>>,
url: &str,
) -> Result<Value, Self::Error> {
let url = url.parse()?;
let time_created = Utc::now();
let signed = match signing_algorithm {
SigningAlgorithm::RsaSha256 => self.sign_request(
signing_key,
signing_algorithm, &SigningInputGetRsaSha256 {
request_target: RequestTarget {
url: &url,
method: Method::GET,
},
host: HostPseudoHeader(&url),
date: DateHeader(time_created),
expires: expires.map(ExpiresPseudoHeader),
},
).await?,
SigningAlgorithm::Hs2019 => self.sign_request(
signing_key,
signing_algorithm,
&SigningInputGetHs2019 {
request_target: RequestTarget {
url: &url,
method: Method::GET,
},
host: HostPseudoHeader(&url),
created: CreatedPseudoHeader(time_created),
expires: expires.map(ExpiresPseudoHeader),
},
).await?,
};
let mut headers = HeaderMap::new();
if !matches!(signing_algorithm, SigningAlgorithm::Hs2019) {
headers.insert(
http::header::DATE,
HeaderValue::try_from(httpdate::fmt_http_date(time_created.into()))
.expect("date should always be a valid header value"),
);
}
headers.insert(
HeaderName::from_lowercase(b"signature").unwrap(),
HeaderValue::try_from(signed.to_string())?,
);
Ok(self
.client
.as_ref()
.as_ref()
.get(url)
.accept(ContentActivityStreams)
.headers(headers)
.json()
.await?)
}
async fn signed_post(
&self,
signing_key: ApSigningKey<'_>,
signing_algorithm: SigningAlgorithm,
expires: Option<chrono::DateTime<Utc>>,
url: &str,
body: &Value,
) -> Result<String, Self::Error> {
let url = url.parse()?;
let body_bytes = serde_json::to_vec(body)?;
// Move in, move out :3
let (digest_raw, body_bytes) = task::spawn_blocking(move || {
let mut sha = sha2::Sha256::new();
sha.update(&body_bytes);
(sha.finalize(), body_bytes)
}).await?;
use base64::prelude::*;
let digest_base64 = format!("sha-256={}", BASE64_STANDARD.encode(digest_raw));
let time_created = Utc::now();
let signed = match signing_algorithm {
SigningAlgorithm::RsaSha256 => self.sign_request(
signing_key,
signing_algorithm,
&SigningInputPostRsaSha256 {
request_target: RequestTarget {
url: &url,
method: Method::POST,
},
host: HostPseudoHeader(&url),
date: DateHeader(time_created),
digest: DigestHeader(&digest_base64),
expires: expires.map(ExpiresPseudoHeader),
},
).await?,
SigningAlgorithm::Hs2019 => self.sign_request(
signing_key,
signing_algorithm,
&SigningInputPostHs2019 {
request_target: RequestTarget {
url: &url,
method: Method::POST,
},
host: HostPseudoHeader(&url),
created: CreatedPseudoHeader(time_created),
digest: DigestHeader(&digest_base64),
expires: expires.map(ExpiresPseudoHeader),
},
).await?,
};
let mut headers = HeaderMap::new();
if !matches!(signing_algorithm, SigningAlgorithm::Hs2019) {
headers.insert(
http::header::DATE,
HeaderValue::try_from(httpdate::fmt_http_date(time_created.into()))
.expect("date should always be a valid header value"),
);
}
headers.insert(
HeaderName::from_lowercase(b"digest").unwrap(),
HeaderValue::try_from(digest_base64)?,
);
headers.insert(
HeaderName::from_lowercase(b"signature").unwrap(),
HeaderValue::try_from(signed.to_string())?,
);
Ok(self
.client
.as_ref()
.as_ref()
.builder(Method::POST, url)
.content_type(ContentActivityStreams)
.headers(headers)
.body(body_bytes)
.send()
.map_ok(String::from_utf8)
.await??
.to_string())
}
}
#[cfg(test)]
mod test {
use std::{borrow::Cow, sync::Arc};
use headers::UserAgent;
use miette::IntoDiagnostic;
use rsa::pkcs8::DecodePrivateKey;
use crate::{
ap_client::ApClientServiceDefaultProvider,
client::federation_client::FederationClient,
crypto::{ApHttpPrivateKey, SigningAlgorithm},
ApClientService,
};
#[tokio::test]
async fn should_request() -> miette::Result<()> {
let key_id = std::env::var("MAG_TEST_KEY_ID").into_diagnostic()?;
let key = std::env::var("MAG_TEST_PRIVATE_KEY").into_diagnostic()?;
let url = std::env::var("MAG_TEST_FETCH_URL").into_diagnostic()?;
let rsa_key = rsa::RsaPrivateKey::from_pkcs8_pem(key.trim()).into_diagnostic()?;
let ap_client = ApClientServiceDefaultProvider {
client: Arc::new(Box::new(
FederationClient::new(
true,
128_000,
25,
UserAgent::from_static("magnetar/0.42 (https://astolfo.social)"),
)
.into_diagnostic()?,
)),
};
let val = ap_client
.signed_get(
ApHttpPrivateKey::Rsa(Cow::Owned(Box::new(rsa_key)))
.create_signing_key(&key_id, SigningAlgorithm::RsaSha256)
.into_diagnostic()?,
SigningAlgorithm::RsaSha256,
None,
&url,
)
.await
.into_diagnostic()?;
println!("{:#?}", val);
Ok(())
}
}

View File

@ -0,0 +1,178 @@
use async_stream::stream;
use futures_util::{select, stream::StreamExt, FutureExt, Stream, TryStreamExt};
use headers::UserAgent;
use hyper::body::Bytes;
use reqwest::{redirect::Policy, Client, RequestBuilder};
use serde_json::Value;
use thiserror::Error;
use tokio::pin;
use url::Url;
use magnetar_core::web_model::ContentType;
#[derive(Debug, Clone)]
pub struct FederationClient {
pub client: Client,
pub body_limit: usize,
pub timeout_seconds: u64,
user_agent: String,
}
#[derive(Debug, Error)]
pub enum FederationClientBuilderError {
#[error("Reqwest error: {0}")]
ReqwestError(#[from] reqwest::Error),
}
#[derive(Debug, Error)]
pub enum FederationClientError {
#[error("Fetch timed out")]
TimeoutError,
#[error("Reqwest error: {0}")]
ReqwestError(#[from] reqwest::Error),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("XML error: {0}")]
XmlError(#[from] quick_xml::de::DeError),
#[error("Body limit exceeded error")]
BodyLimitExceededError,
#[error("Invalid URL: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error("Client error: {0}")]
Other(String),
}
#[derive(Debug)]
pub struct FederationRequestBuilder<'a> {
client: &'a FederationClient,
builder: RequestBuilder,
}
impl FederationClient {
pub fn new(
force_https: bool,
body_limit: usize,
timeout_seconds: u64,
user_agent: UserAgent,
) -> Result<FederationClient, FederationClientBuilderError> {
let client = Client::builder()
.https_only(force_https)
.redirect(Policy::limited(5))
.build()?;
Ok(FederationClient {
client,
body_limit,
timeout_seconds,
user_agent: user_agent.to_string(),
})
}
pub fn builder(&self, method: reqwest::Method, url: Url) -> FederationRequestBuilder<'_> {
FederationRequestBuilder {
client: self,
builder: self
.client
.request(method, url)
.header(http::header::USER_AGENT, self.user_agent.clone()),
}
}
pub fn get(&self, url: Url) -> FederationRequestBuilder<'_> {
self.builder(reqwest::Method::GET, url)
}
}
impl FederationRequestBuilder<'_> {
pub fn content_type(self, content_type: impl ContentType) -> Self {
Self {
client: self.client,
builder: self.builder.header(
http::header::CONTENT_TYPE,
content_type.mime_type().to_string(),
),
}
}
pub fn accept(self, content_type: impl ContentType) -> Self {
Self {
client: self.client,
builder: self
.builder
.header(http::header::ACCEPT, content_type.mime_type().to_string()),
}
}
pub fn headers(self, headers: reqwest::header::HeaderMap) -> Self {
Self {
client: self.client,
builder: self.builder.headers(headers),
}
}
pub fn body(self, body: Vec<u8>) -> Self {
Self {
client: self.client,
builder: self.builder.body(body),
}
}
async fn send_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, FederationClientError>>, FederationClientError>
{
let mut body = self
.builder
.send()
.await?
.error_for_status()?
.bytes_stream()
.map(|b| b.map_err(FederationClientError::ReqwestError));
let body_limit = self.client.body_limit;
let mut partial_length: usize = 0;
Ok(stream! {
while let Some(chunk) = body.next().await.transpose()? {
if partial_length + chunk.len() > body_limit {
yield Err(FederationClientError::BodyLimitExceededError);
}
partial_length += chunk.len();
yield Ok(chunk);
}
})
}
pub async fn send(self) -> Result<Vec<u8>, FederationClientError> {
let sleep = tokio::time::sleep(tokio::time::Duration::from_secs(
self.client.timeout_seconds,
))
.fuse();
tokio::pin!(sleep);
let body = async move {
self.send_stream()
.await?
.try_fold(Vec::new(), |mut acc, b| async move {
acc.extend_from_slice(&b);
Ok(acc)
})
.await
}
.fuse();
pin!(body);
select! {
b = body => b,
_ = sleep => Err(FederationClientError::TimeoutError)
}
}
pub async fn json(self) -> Result<Value, FederationClientError> {
let data = self.send().await?;
let json = serde_json::from_slice::<Value>(&data)?;
Ok(json)
}
}

View File

@ -0,0 +1 @@
pub mod federation_client;

View File

@ -0,0 +1,323 @@
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::pkcs8::DecodePrivateKey;
use rsa::pkcs8::DecodePublicKey;
use rsa::signature::Verifier;
use rsa::{
sha2::{Sha256, Sha512},
signature::Signer,
};
use serde::{Deserialize, Serialize};
use std::fmt::Formatter;
use std::str::FromStr;
use std::{borrow::Cow, fmt::Display};
use strum::AsRefStr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ApVerificationError {
#[error("Signature algorithm and public key mismatch: {0} not compatible with {1}")]
KeyAlgorithmMismatch(SigningAlgorithm, String),
#[error("PKCS#1 v1.5 RSA signature verification failed: {0}")]
RsaSignatureError(#[from] rsa::signature::Error),
}
#[derive(Debug, Error)]
pub enum ApSigningError {
#[error("Signature algorithm and public key mismatch: {0} not compatible with {1}")]
KeyAlgorithmMismatch(SigningAlgorithm, String),
#[error("Signing error: {0}")]
RsaSigningError(#[from] rsa::signature::Error),
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum SigningAlgorithm {
#[serde(rename = "hs2019")]
Hs2019,
#[serde(rename = "rsa-sha256")]
RsaSha256,
}
impl Display for SigningAlgorithm {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Hs2019 => write!(f, "hs2019"),
Self::RsaSha256 => write!(f, "rsa-sha256"),
}
}
}
#[derive(Debug, Clone, AsRefStr)]
pub enum ApHttpVerificationKey<'a> {
#[strum(serialize = "rsa-sha256")]
RsaSha256(Cow<'a, rsa::pkcs1v15::VerifyingKey<Sha256>>),
#[strum(serialize = "rsa-sha512")]
RsaSha512(Cow<'a, rsa::pkcs1v15::VerifyingKey<Sha512>>),
#[strum(serialize = "ed25519")]
Ed25519(Cow<'a, ed25519_dalek::VerifyingKey>),
}
#[derive(Debug, Clone, AsRefStr)]
pub enum ApHttpPublicKey<'a> {
#[strum(serialize = "rsa")]
Rsa(Cow<'a, rsa::RsaPublicKey>),
#[strum(serialize = "ed25519")]
Ed25519(Cow<'a, ed25519_dalek::VerifyingKey>),
}
#[derive(Debug, Copy, Clone, Error)]
#[error("Failed to parse the public key: No available parser could decode the PEM string")]
pub struct ApHttpPublicKeyParseError;
impl FromStr for ApHttpPublicKey<'_> {
type Err = ApHttpPublicKeyParseError;
fn from_str(input_pem: &str) -> Result<Self, Self::Err> {
let pem = input_pem.trim();
let parse_pkcs1_rsa: &dyn Fn(_) -> _ = &|p| {
Some(ApHttpPublicKey::Rsa(Cow::Owned(
rsa::RsaPublicKey::from_pkcs1_pem(p).ok()?,
)))
};
let parse_spki_rsa: &dyn Fn(_) -> _ = &|p| {
Some(ApHttpPublicKey::Rsa(Cow::Owned(
rsa::RsaPublicKey::from_public_key_pem(p).ok()?,
)))
};
let parse_spki_ed25519: &dyn Fn(_) -> _ = &|p| {
Some(ApHttpPublicKey::Ed25519(Cow::Owned(
ed25519_dalek::VerifyingKey::from_public_key_pem(p).ok()?,
)))
};
// Some heuristics
let parsers: &[_] = match pem {
p if p.starts_with("-----BEGIN PUBLIC KEY-----") => {
&[parse_spki_rsa, parse_spki_ed25519]
}
p if p.starts_with("-----BEGIN RSA PUBLIC KEY-----") => &[parse_pkcs1_rsa],
_ => &[parse_spki_rsa, parse_spki_ed25519, parse_pkcs1_rsa],
};
for parser in parsers {
if let Some(k) = parser(pem) {
return Ok(k);
}
}
Err(ApHttpPublicKeyParseError)
}
}
impl ApHttpVerificationKey<'_> {
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), ApVerificationError> {
match self {
ApHttpVerificationKey::RsaSha256(rsa_pubkey) => {
let sig = rsa::pkcs1v15::Signature::try_from(signature)?;
Ok(rsa_pubkey.verify(message, &sig)?)
}
ApHttpVerificationKey::RsaSha512(rsa_pubkey) => {
let sig = rsa::pkcs1v15::Signature::try_from(signature)?;
Ok(rsa_pubkey.verify(message, &sig)?)
}
ApHttpVerificationKey::Ed25519(ed25519_pubkey) => {
let sig = ed25519_dalek::Signature::try_from(signature)?;
Ok(ed25519_pubkey.verify_strict(message, &sig)?)
}
}
}
}
impl ApHttpPublicKey<'_> {
pub fn verify(
&self,
algorithm: SigningAlgorithm,
message: &[u8],
signature: &[u8],
) -> Result<(), ApVerificationError> {
match (self, algorithm) {
(Self::Rsa(key), SigningAlgorithm::Hs2019) => {
let verification_key = ApHttpVerificationKey::RsaSha256(Cow::Owned(
rsa::pkcs1v15::VerifyingKey::new(key.clone().into_owned()),
));
let Err(_) = verification_key.verify(message, signature) else {
return Ok(());
};
let verification_key = ApHttpVerificationKey::RsaSha512(Cow::Owned(
rsa::pkcs1v15::VerifyingKey::new(key.clone().into_owned()),
));
Ok(verification_key.verify(message, signature)?)
}
(Self::Rsa(key), SigningAlgorithm::RsaSha256) => {
let verification_key = ApHttpVerificationKey::RsaSha256(Cow::Owned(
rsa::pkcs1v15::VerifyingKey::new(key.clone().into_owned()),
));
Ok(verification_key.verify(message, signature)?)
}
(_, SigningAlgorithm::RsaSha256) => Err(ApVerificationError::KeyAlgorithmMismatch(
algorithm,
self.as_ref().to_owned(),
)),
(Self::Ed25519(key), SigningAlgorithm::Hs2019) => {
let verification_key = ApHttpVerificationKey::Ed25519(Cow::Borrowed(key.as_ref()));
Ok(verification_key.verify(message, signature)?)
}
}
}
}
#[derive(Debug, Clone, AsRefStr)]
pub enum ApHttpPrivateKey<'a> {
#[strum(serialize = "rsa")]
Rsa(Cow<'a, Box<rsa::RsaPrivateKey>>),
#[strum(serialize = "ed25519")]
Ed25519(Cow<'a, ed25519_dalek::SecretKey>),
}
#[derive(Debug, Copy, Clone, Error)]
#[error("Failed to parse the private key: No available parser could decode the PEM string")]
pub struct ApHttpPrivateKeyParseError;
impl FromStr for ApHttpPrivateKey<'_> {
type Err = ApHttpPrivateKeyParseError;
fn from_str(input_pem: &str) -> Result<Self, Self::Err> {
let pem = input_pem.trim();
let parse_pkcs1_rsa: &dyn Fn(_) -> _ = &|p| {
Some(ApHttpPrivateKey::Rsa(Cow::Owned(Box::new(
rsa::RsaPrivateKey::from_pkcs1_pem(p).ok()?,
))))
};
let parse_pkcs8_rsa: &dyn Fn(_) -> _ = &|p| {
Some(ApHttpPrivateKey::Rsa(Cow::Owned(Box::new(
rsa::RsaPrivateKey::from_pkcs8_pem(p).ok()?,
))))
};
let parse_pkcs8_ed25519: &dyn Fn(_) -> _ = &|p| {
Some(ApHttpPrivateKey::Ed25519(Cow::Owned(
ed25519_dalek::SigningKey::from_pkcs8_pem(p)
.ok()?
.to_bytes(),
)))
};
// Some heuristics
let parsers: &[_] = match pem {
p if p.contains("-----BEGIN PRIVATE KEY-----") => {
&[parse_pkcs8_rsa, parse_pkcs8_ed25519]
}
p if p.contains("-----BEGIN RSA PRIVATE KEY-----") => &[parse_pkcs1_rsa],
_ => &[parse_pkcs8_rsa, parse_pkcs8_ed25519, parse_pkcs1_rsa],
};
for parser in parsers {
if let Some(k) = parser(pem) {
return Ok(k);
}
}
Err(ApHttpPrivateKeyParseError)
}
}
#[derive(Debug, Clone, AsRefStr)]
pub enum ApHttpSigningKey<'a> {
#[strum(serialize = "rsa-sha256")]
RsaSha256(Cow<'a, rsa::pkcs1v15::SigningKey<Sha256>>),
#[strum(serialize = "rsa-sha512")]
RsaSha512(Cow<'a, rsa::pkcs1v15::SigningKey<Sha512>>),
#[strum(serialize = "ed25519")]
Ed25519(Cow<'a, ed25519_dalek::SigningKey>),
}
#[derive(Debug, Clone)]
pub struct ApSigningKey<'a> {
pub key: ApHttpSigningKey<'a>,
pub key_id: Cow<'a, str>,
}
impl<'a> ApSigningKey<'a> {
pub fn into_owned(self) -> ApSigningKey<'static> {
ApSigningKey {
key: self.key.into_owned(),
key_id: Cow::Owned(self.key_id.into_owned()),
}
}
}
impl<'a> ApHttpSigningKey<'a> {
pub fn into_owned(self) -> ApHttpSigningKey<'static> {
match self {
ApHttpSigningKey::RsaSha256(k) => ApHttpSigningKey::RsaSha256(Cow::Owned(k.into_owned())),
ApHttpSigningKey::RsaSha512(k) => ApHttpSigningKey::RsaSha512(Cow::Owned(k.into_owned())),
ApHttpSigningKey::Ed25519(k) => ApHttpSigningKey::Ed25519(Cow::Owned(k.into_owned())),
}
}
}
impl ApHttpSigningKey<'_> {
pub fn sign(
&self,
algorithm: SigningAlgorithm,
message: &[u8],
) -> Result<Vec<u8>, ApSigningError> {
match (self, algorithm) {
(Self::RsaSha256(key), SigningAlgorithm::RsaSha256 | SigningAlgorithm::Hs2019) => {
Ok(Box::<[u8]>::from(key.sign(message)).into_vec())
}
(Self::RsaSha512(key), SigningAlgorithm::Hs2019) => {
Ok(Box::<[u8]>::from(key.sign(message)).into_vec())
}
(Self::Ed25519(key), SigningAlgorithm::Hs2019) => {
Ok(key.sign(message).to_bytes().to_vec())
}
(key, _) => Err(ApSigningError::KeyAlgorithmMismatch(
algorithm,
key.as_ref().to_string(),
)),
}
}
pub fn sign_base64(
&self,
algorithm: SigningAlgorithm,
message: &[u8],
) -> Result<String, ApSigningError> {
let signed = self.sign(algorithm, message)?;
use base64::prelude::*;
Ok(BASE64_STANDARD.encode(signed))
}
}
impl ApHttpPrivateKey<'_> {
pub fn create_signing_key<'a>(
&'a self,
key_id: &'a str,
algorithm: SigningAlgorithm,
) -> Result<ApSigningKey<'a>, ApSigningError> {
Ok(ApSigningKey {
key_id: Cow::Borrowed(key_id),
key: match (self, algorithm) {
(Self::Rsa(key), SigningAlgorithm::RsaSha256 | SigningAlgorithm::Hs2019) => {
ApHttpSigningKey::RsaSha256(Cow::Owned(rsa::pkcs1v15::SigningKey::new(
*key.as_ref().to_owned(),
)))
}
(Self::Ed25519(key), SigningAlgorithm::Hs2019) => ApHttpSigningKey::Ed25519(
Cow::Owned(ed25519_dalek::SigningKey::from_bytes(key.as_ref())),
),
(key, _) => {
return Err(ApSigningError::KeyAlgorithmMismatch(
algorithm,
key.as_ref().to_owned(),
))
}
},
})
}
}

176
ext_federation/src/lib.rs Normal file
View File

@ -0,0 +1,176 @@
use chrono::{DateTime, Utc};
use indexmap::IndexSet;
use magnetar_common::util::{FediverseTag, ValidName};
use magnetar_host_meta::Xrd;
use magnetar_webfinger::webfinger::WebFinger;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use strum::AsRefStr;
use url::{Host, Url};
use crate::crypto::{ApSigningKey, SigningAlgorithm};
pub mod ap_client;
pub mod client;
pub mod crypto;
pub mod lookup_flow;
/// The *visible* domain of fediverse handles, that gets resolved by WebFinger
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct HostUnmapped(Host);
impl AsRef<Host> for HostUnmapped {
fn as_ref(&self) -> &Host {
&self.0
}
}
/// The real domain of fediverse handles used for federation
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct HostMapped(Host);
impl AsRef<Host> for HostMapped {
fn as_ref(&self) -> &Host {
&self.0
}
}
#[async_trait::async_trait]
pub trait HostMetaResolverService: Send + Sync {
type Error;
async fn resolve(&self, host: &HostUnmapped) -> Result<Xrd, Self::Error>;
}
#[async_trait::async_trait]
pub trait WebFingerResolverService: Send + Sync {
type Error;
async fn resolve_url(&self, url: &str) -> Result<WebFinger, Self::Error>;
async fn resolve(
&self,
template_url: &str,
resolved_uri: &str,
) -> Result<WebFinger, Self::Error> {
self.resolve_url(
&template_url.replace(
"{uri}",
&percent_encoding::utf8_percent_encode(
resolved_uri,
percent_encoding::NON_ALPHANUMERIC,
)
.to_string(),
),
)
.await
}
}
#[derive(Debug)]
pub struct UnmappedUser {
pub name: ValidName,
pub host: HostUnmapped,
}
impl From<&UnmappedUser> for FediverseTag {
fn from(user: &UnmappedUser) -> Self {
FediverseTag {
name: user.name.clone(),
host: Some(user.host.as_ref().clone()),
}
}
}
#[derive(Debug)]
pub struct MappedUser {
pub host_meta: Option<Xrd>,
pub webfinger: WebFinger,
pub tag_mapped: FediverseTag,
pub ap_url: Url,
}
#[async_trait::async_trait]
pub trait FederationLookupService {
type Error;
async fn map_fedi_tag(&self, user: &UnmappedUser) -> Result<MappedUser, Self::Error>;
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, AsRefStr)]
pub enum ApSigningField {
#[serde(rename = "(request-target)")]
#[strum(serialize = "(request-target)")]
PseudoRequestTarget,
#[serde(rename = "(expires)")]
#[strum(serialize = "(expires)")]
PseudoExpires,
#[serde(rename = "(created)")]
#[strum(serialize = "(created)")]
PseudoCreated,
#[serde(rename = "date")]
#[strum(serialize = "date")]
Date,
#[serde(rename = "host")]
#[strum(serialize = "host")]
Host,
#[serde(rename = "digest")]
#[strum(serialize = "digest")]
Digest,
#[serde(rename = "content-length")]
#[strum(serialize = "content-length")]
ContentLength,
}
#[derive(Debug)]
pub struct ApSignature {
pub key_id: String,
pub algorithm: Option<SigningAlgorithm>,
pub created: Option<DateTime<Utc>>,
pub expires: Option<DateTime<Utc>>,
pub headers: Option<ApSigningHeaders>,
pub signature: String,
}
#[derive(Debug)]
pub struct ApSigningHeaders(pub(crate) IndexSet<ApSigningField>);
pub trait SigningParts: Send {
fn get_created(&self) -> Option<&DateTime<Utc>>;
fn get_expires(&self) -> Option<&DateTime<Utc>>;
}
pub trait SigningInput: SigningParts + Sync {
fn create_signing_input(&self) -> Vec<(ApSigningField, String)>;
}
#[async_trait::async_trait]
pub trait ApClientService: Send + Sync {
type Error;
async fn sign_request(
&self,
signing_key: ApSigningKey<'_>,
signing_algorithm: SigningAlgorithm,
request: &dyn SigningInput,
) -> Result<ApSignature, Self::Error>;
async fn signed_get(
&self,
signing_key: ApSigningKey<'_>,
signing_algorithm: SigningAlgorithm,
expires: Option<chrono::DateTime<Utc>>,
url: &str,
) -> Result<Value, Self::Error>;
async fn signed_post(
&self,
signing_key: ApSigningKey<'_>,
signing_algorithm: SigningAlgorithm,
expires: Option<chrono::DateTime<Utc>>,
url: &str,
body: &Value,
) -> Result<String, Self::Error>;
}

View File

@ -0,0 +1,221 @@
use std::{io::Cursor, sync::Arc};
use magnetar_common::{
config::MagnetarNetworkingProtocol,
util::{FediverseTag, FediverseTagDisplay, FediverseTagParseError},
};
use magnetar_core::web_model::{acct::Acct, content_type::ContentJrdJson};
use magnetar_host_meta::{Xrd, XrdXml};
use magnetar_webfinger::webfinger::{WebFinger, WebFingerRel, WebFingerSubject};
use thiserror::Error;
use tracing::trace;
use url::Url;
use crate::{
client::federation_client::{FederationClient, FederationClientError},
FederationLookupService, HostMetaResolverService, HostUnmapped, MappedUser, UnmappedUser,
WebFingerResolverService,
};
pub struct HostMetaResolverProviderDefault {
client: Arc<FederationClient>,
protocol: MagnetarNetworkingProtocol,
}
#[async_trait::async_trait]
impl HostMetaResolverService for HostMetaResolverProviderDefault {
type Error = FederationClientError;
async fn resolve(&self, HostUnmapped(host): &HostUnmapped) -> Result<Xrd, Self::Error> {
let host_meta_xml = self
.client
.get(Url::parse(&format!(
"{}://{}/.well-known/host-meta",
self.protocol.as_ref(),
host
))?)
.send()
.await?;
let XrdXml::Xrd(xrd) = quick_xml::de::from_reader(Cursor::new(host_meta_xml))?;
Ok(xrd)
}
}
pub struct WebFingerResolverProviderDefault {
client: Arc<FederationClient>,
}
#[async_trait::async_trait]
impl WebFingerResolverService for WebFingerResolverProviderDefault {
type Error = FederationClientError;
async fn resolve_url(&self, url: &str) -> Result<WebFinger, Self::Error> {
let host_meta_xml = self
.client
.get(Url::parse(url)?)
.content_type(ContentJrdJson)
.send()
.await?;
let webfinger = serde_json::from_reader(Cursor::new(host_meta_xml))?;
Ok(webfinger)
}
}
#[derive(Debug, Error)]
pub enum FederationLookupErrror {
#[error("Federation client error: {0}")]
FederationClientError(#[from] FederationClientError),
#[error("Fediverse tag parse error: {0}")]
FediverseTagParseError(#[from] FediverseTagParseError),
#[error("URL parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("Missing ActivityStreams URL in WebFinger")]
MissingApUrl,
#[error("Missing Acct URI in WebFinger")]
MissingAcctUri,
}
#[derive(Clone)]
pub struct FederationLookupServiceProviderDefault {
host_meta_resolver: Arc<dyn HostMetaResolverService<Error = FederationClientError>>,
webfinger_resolver: Arc<dyn WebFingerResolverService<Error = FederationClientError>>,
protocol: MagnetarNetworkingProtocol,
}
impl FederationLookupServiceProviderDefault {
pub fn new(
host_meta_resolver: Arc<dyn HostMetaResolverService<Error = FederationClientError>>,
webfinger_resolver: Arc<dyn WebFingerResolverService<Error = FederationClientError>>,
protocol: MagnetarNetworkingProtocol,
) -> Self {
Self {
host_meta_resolver,
webfinger_resolver,
protocol,
}
}
}
#[async_trait::async_trait]
impl FederationLookupService for FederationLookupServiceProviderDefault {
type Error = FederationLookupErrror;
#[tracing::instrument(level = "trace", skip(self))]
async fn map_fedi_tag(
&self,
user: &UnmappedUser,
) -> Result<MappedUser, FederationLookupErrror> {
trace!("Fetching flow initiated");
let host_meta = self.host_meta_resolver.resolve(&user.host).await;
let webfinger_template = match &host_meta {
Ok(h) => {
trace!("host-meta found: {:?}", h);
h.get_webfinger_template().map(str::to_owned)
}
Err(e) => {
trace!("host-meta fetch failed: {}", e);
None
}
}
.unwrap_or_else(|| {
Xrd::default_host_meta(self.protocol.as_ref(), &user.host.as_ref().to_string())
.get_webfinger_template()
.expect("default WebFinger template")
.to_owned()
});
let webfinger = self
.webfinger_resolver
.resolve(
&webfinger_template,
Acct::from(FediverseTag::from(user)).as_ref(),
)
.await?;
trace!("Webfinger fetched: {:?}", webfinger);
let real_tag = match &webfinger.subject {
WebFingerSubject::Acct(acct) => Some(acct.clone()),
_ => webfinger
.aliases
.iter()
.flatten()
.find_map(|alias| match alias {
WebFingerSubject::Acct(acct) => Some(acct.clone()),
_ => None,
}),
}
.ok_or(FederationLookupErrror::MissingAcctUri)?;
let ap_url = webfinger
.links
.iter()
.find_map(|link| match link {
WebFingerRel::RelSelf { href, .. } | WebFingerRel::RelSelfAlt { href, .. } => {
Some(href)
}
_ => None,
})
.ok_or(FederationLookupErrror::MissingApUrl)?
.parse()?;
Ok(MappedUser {
host_meta: host_meta.ok(),
webfinger,
tag_mapped: FediverseTag::try_from(&FediverseTagDisplay::try_from(&real_tag)?)?,
ap_url,
})
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use headers::UserAgent;
use magnetar_common::{config::MagnetarNetworkingProtocol, util::FediverseTag};
use tracing::{info, Level};
use crate::{
client::federation_client::FederationClient, FederationLookupService, HostUnmapped,
UnmappedUser,
};
use super::{
FederationLookupServiceProviderDefault, HostMetaResolverProviderDefault,
WebFingerResolverProviderDefault,
};
#[tokio::test]
async fn should_resolve() {
tracing_subscriber::fmt()
.with_max_level(Level::TRACE)
.init();
let client = Arc::new(
FederationClient::new(true, 64000, 20, UserAgent::from_static("magnetar/0.42"))
.unwrap(),
);
let federation_lookup = FederationLookupServiceProviderDefault::new(
Arc::new(HostMetaResolverProviderDefault {
protocol: MagnetarNetworkingProtocol::Https,
client: client.clone(),
}),
Arc::new(WebFingerResolverProviderDefault { client }),
MagnetarNetworkingProtocol::Https,
);
let tag: FediverseTag = "@natty@astolfo.social".parse().unwrap();
info!("Resolving: {}", tag);
let resolved = federation_lookup
.map_fedi_tag(&UnmappedUser {
name: tag.name,
host: HostUnmapped(tag.host.unwrap()),
})
.await
.unwrap();
info!("Resolved: {:#?}", resolved);
}
}

12
ext_host_meta/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "magnetar_host_meta"
version.workspace = true
edition.workspace = true
[lib]
crate-type = ["rlib"]
[dependencies]
magnetar_core = { path = "../core" }
serde = { workspace = true, features = ["derive"] }
quick-xml = { workspace = true, features = ["serialize", "overlapped-lists"] }

97
ext_host_meta/src/lib.rs Normal file
View File

@ -0,0 +1,97 @@
use magnetar_core::web_model::{content_type::ContentXrdXml, rel::RelLrdd, Rel};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum XrdXml {
#[serde(rename = "XRD")]
Xrd(Xrd),
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Xrd {
#[serde(rename = "@xmlns")]
xmlns: String,
#[serde(rename = "Link")]
links: Vec<Link>,
}
impl Xrd {
pub fn default_host_meta(protocol: &str, domain: &str) -> Xrd {
Xrd {
xmlns: r#"http://docs.oasis-open.org/ns/xri/xrd-1.0"#.to_string(),
links: vec![Link {
rel: Some(RelLrdd.rel().to_string()),
r#type: Some(ContentXrdXml.as_ref().to_string()),
href: None,
template: Some(format!(
"{}://{}/.well-known/webfinger?resource={{uri}}",
protocol, domain
)),
}],
}
}
pub fn get_webfinger_template(&self) -> Option<&str> {
self.links
.iter()
.find(|l| {
l.r#type.as_deref() == Some(ContentXrdXml.as_ref())
&& l.rel.as_deref() == Some(RelLrdd.as_ref())
})
.and_then(|l| l.template.as_deref())
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename = "Link")]
pub struct Link {
#[serde(rename = "@rel")]
#[serde(skip_serializing_if = "Option::is_none")]
rel: Option<String>,
#[serde(rename = "@type")]
#[serde(skip_serializing_if = "Option::is_none")]
r#type: Option<String>,
#[serde(rename = "@href")]
#[serde(skip_serializing_if = "Option::is_none")]
href: Option<String>,
#[serde(rename = "@template")]
#[serde(skip_serializing_if = "Option::is_none")]
template: Option<String>,
}
#[cfg(test)]
mod test {
use std::error::Error;
use crate::{Xrd, XrdXml};
#[test]
fn should_parse_host_meta() -> Result<(), Box<dyn Error>> {
let xml = r#"
<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/xrd+xml" template="https://example.org/.well-known/webfinger?resource={uri}"/>
</XRD>
"#;
let XrdXml::Xrd(xrd) = quick_xml::de::from_str::<XrdXml>(xml)?;
assert_eq!(xrd, Xrd::default_host_meta("https", "example.org"));
Ok(())
}
#[test]
fn should_fail_on_invalid_root_tag() {
let xml = r#"
<?xml version="1.0" encoding="UTF-8"?>
<ABC xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/xrd+xml" template="https://example.org/.well-known/webfinger?resource={uri}"/>
</ABC>
"#;
let xrd = quick_xml::de::from_str::<XrdXml>(xml);
assert!(xrd.is_err());
}
}

View File

@ -1,5 +1,5 @@
[package]
name = "magnetar_calckey_model"
name = "magnetar_model"
version.workspace = true
edition.workspace = true
@ -8,20 +8,26 @@ crate-type = ["rlib"]
[dependencies]
ck = { path = "./entity_ck" }
ext_calckey_model_migration = { path = "./migration" }
ext_model_migration = { path = "./migration" }
magnetar_common = { path = "../magnetar_common" }
magnetar_sdk = { path = "../magnetar_sdk" }
dotenvy = { workspace = true}
dotenvy = { workspace = true }
futures-core = { workspace = true }
futures-util = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tokio-util = { workspace = true}
redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"]}
sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls", "macros"] }
tokio-util = { workspace = true }
redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"] }
sea-orm = { workspace = true, features = [
"sqlx-postgres",
"runtime-tokio-rustls",
"macros",
] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
strum = { workspace = true }
chrono = { workspace = true }
tracing = { workspace = true }
thiserror = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use super::sea_orm_active_enums::AntennaSrcEnum;
use sea_orm::entity::prelude::*;
@ -27,8 +27,6 @@ pub struct Model {
pub case_sensitive: bool,
#[sea_orm(column_name = "withReplies")]
pub with_replies: bool,
#[sea_orm(column_name = "userGroupJoiningId")]
pub user_group_joining_id: Option<String>,
pub users: Vec<String>,
#[sea_orm(column_name = "excludeKeywords", column_type = "JsonBinary")]
pub exclude_keywords: Json,
@ -48,14 +46,6 @@ pub enum Relation {
on_delete = "Cascade"
)]
User,
#[sea_orm(
belongs_to = "super::user_group_joining::Entity",
from = "Column::UserGroupJoiningId",
to = "super::user_group_joining::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
UserGroupJoining,
#[sea_orm(
belongs_to = "super::user_list::Entity",
from = "Column::UserListId",
@ -78,12 +68,6 @@ impl Related<super::user::Entity> for Entity {
}
}
impl Related<super::user_group_joining::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupJoining.def()
}
}
impl Related<super::user_list::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserList.def()

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@ -65,8 +65,6 @@ pub enum Relation {
on_delete = "SetNull"
)]
DriveFolder,
#[sea_orm(has_many = "super::page::Entity")]
Page,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
@ -83,12 +81,6 @@ impl Related<super::drive_folder::Entity> for Entity {
}
}
impl Related<super::page::Entity> for Entity {
fn to() -> RelationDef {
Relation::Page.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use super::sea_orm_active_enums::MetaSensitivemediadetectionEnum;
use super::sea_orm_active_enums::MetaSensitivemediadetectionsensitivityEnum;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
pub mod prelude;
@ -20,8 +20,6 @@ pub mod drive_folder;
pub mod emoji;
pub mod follow_request;
pub mod following;
pub mod gallery_like;
pub mod gallery_post;
pub mod hashtag;
pub mod instance;
pub mod meta;
@ -36,8 +34,6 @@ pub mod note_thread_muting;
pub mod note_unread;
pub mod note_watching;
pub mod notification;
pub mod page;
pub mod page_like;
pub mod password_reset_request;
pub mod poll;
pub mod poll_vote;
@ -52,10 +48,6 @@ pub mod signin;
pub mod sw_subscription;
pub mod used_username;
pub mod user;
pub mod user_group;
pub mod user_group_invitation;
pub mod user_group_invite;
pub mod user_group_joining;
pub mod user_ip;
pub mod user_keypair;
pub mod user_list;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use super::sea_orm_active_enums::MutedNoteReasonEnum;
use sea_orm::entity::prelude::*;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use super::sea_orm_active_enums::NoteVisibilityEnum;
use sea_orm::entity::prelude::*;
@ -60,6 +60,8 @@ pub struct Model {
pub thread_id: Option<String>,
#[sea_orm(column_name = "updatedAt")]
pub updated_at: Option<DateTimeWithTimeZone>,
pub is_quote: Option<bool>,
pub is_renote: Option<bool>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use super::sea_orm_active_enums::NotificationTypeEnum;
use sea_orm::entity::prelude::*;
@ -24,8 +24,6 @@ pub struct Model {
#[sea_orm(column_name = "followRequestId")]
pub follow_request_id: Option<String>,
pub r#type: NotificationTypeEnum,
#[sea_orm(column_name = "userGroupInvitationId")]
pub user_group_invitation_id: Option<String>,
#[sea_orm(column_name = "customBody")]
pub custom_body: Option<String>,
#[sea_orm(column_name = "customHeader")]
@ -78,14 +76,6 @@ pub enum Relation {
on_delete = "Cascade"
)]
User1,
#[sea_orm(
belongs_to = "super::user_group_invitation::Entity",
from = "Column::UserGroupInvitationId",
to = "super::user_group_invitation::Column::Id",
on_update = "NoAction",
on_delete = "Cascade"
)]
UserGroupInvitation,
}
impl Related<super::access_token::Entity> for Entity {
@ -106,10 +96,4 @@ impl Related<super::note::Entity> for Entity {
}
}
impl Related<super::user_group_invitation::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupInvitation.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use super::sea_orm_active_enums::PollNotevisibilityEnum;
use sea_orm::entity::prelude::*;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
pub use super::abuse_user_report::Entity as AbuseUserReport;
pub use super::access_token::Entity as AccessToken;
@ -18,8 +18,6 @@ pub use super::drive_folder::Entity as DriveFolder;
pub use super::emoji::Entity as Emoji;
pub use super::follow_request::Entity as FollowRequest;
pub use super::following::Entity as Following;
pub use super::gallery_like::Entity as GalleryLike;
pub use super::gallery_post::Entity as GalleryPost;
pub use super::hashtag::Entity as Hashtag;
pub use super::instance::Entity as Instance;
pub use super::meta::Entity as Meta;
@ -34,8 +32,6 @@ pub use super::note_thread_muting::Entity as NoteThreadMuting;
pub use super::note_unread::Entity as NoteUnread;
pub use super::note_watching::Entity as NoteWatching;
pub use super::notification::Entity as Notification;
pub use super::page::Entity as Page;
pub use super::page_like::Entity as PageLike;
pub use super::password_reset_request::Entity as PasswordResetRequest;
pub use super::poll::Entity as Poll;
pub use super::poll_vote::Entity as PollVote;
@ -49,10 +45,6 @@ pub use super::signin::Entity as Signin;
pub use super::sw_subscription::Entity as SwSubscription;
pub use super::used_username::Entity as UsedUsername;
pub use super::user::Entity as User;
pub use super::user_group::Entity as UserGroup;
pub use super::user_group_invitation::Entity as UserGroupInvitation;
pub use super::user_group_invite::Entity as UserGroupInvite;
pub use super::user_group_joining::Entity as UserGroupJoining;
pub use super::user_ip::Entity as UserIp;
pub use super::user_keypair::Entity as UserKeypair;
pub use super::user_list::Entity as UserList;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use super::sea_orm_active_enums::RelayStatusEnum;
use sea_orm::entity::prelude::*;

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@ -8,8 +8,6 @@ use serde::{Deserialize, Serialize};
pub enum AntennaSrcEnum {
#[sea_orm(string_value = "all")]
All,
#[sea_orm(string_value = "group")]
Group,
#[sea_orm(string_value = "home")]
Home,
#[sea_orm(string_value = "instances")]
@ -100,14 +98,10 @@ pub enum NotificationTypeEnum {
Follow,
#[sea_orm(string_value = "followRequestAccepted")]
FollowRequestAccepted,
#[sea_orm(string_value = "groupInvited")]
GroupInvited,
#[sea_orm(string_value = "mention")]
Mention,
#[sea_orm(string_value = "pollEnded")]
PollEnded,
#[sea_orm(string_value = "pollVote")]
PollVote,
#[sea_orm(string_value = "quote")]
Quote,
#[sea_orm(string_value = "reaction")]
@ -120,20 +114,6 @@ pub enum NotificationTypeEnum {
Reply,
}
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy, Serialize, Deserialize)]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
enum_name = "page_visibility_enum"
)]
pub enum PageVisibilityEnum {
#[sea_orm(string_value = "followers")]
Followers,
#[sea_orm(string_value = "public")]
Public,
#[sea_orm(string_value = "specified")]
Specified,
}
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy, Serialize, Deserialize)]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -173,35 +153,3 @@ pub enum UserProfileFfvisibilityEnum {
#[sea_orm(string_value = "public")]
Public,
}
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy, Serialize, Deserialize)]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
enum_name = "user_profile_mutingnotificationtypes_enum"
)]
pub enum UserProfileMutingnotificationtypesEnum {
#[sea_orm(string_value = "app")]
App,
#[sea_orm(string_value = "follow")]
Follow,
#[sea_orm(string_value = "followRequestAccepted")]
FollowRequestAccepted,
#[sea_orm(string_value = "groupInvited")]
GroupInvited,
#[sea_orm(string_value = "mention")]
Mention,
#[sea_orm(string_value = "pollEnded")]
PollEnded,
#[sea_orm(string_value = "pollVote")]
PollVote,
#[sea_orm(string_value = "quote")]
Quote,
#[sea_orm(string_value = "reaction")]
Reaction,
#[sea_orm(string_value = "receiveFollowRequest")]
ReceiveFollowRequest,
#[sea_orm(string_value = "renote")]
Renote,
#[sea_orm(string_value = "reply")]
Reply,
}

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@ -106,10 +106,6 @@ pub enum Relation {
DriveFile1,
#[sea_orm(has_many = "super::drive_folder::Entity")]
DriveFolder,
#[sea_orm(has_many = "super::gallery_like::Entity")]
GalleryLike,
#[sea_orm(has_many = "super::gallery_post::Entity")]
GalleryPost,
#[sea_orm(has_many = "super::meta::Entity")]
Meta,
#[sea_orm(has_many = "super::moderation_log::Entity")]
@ -128,10 +124,6 @@ pub enum Relation {
NoteUnread,
#[sea_orm(has_many = "super::note_watching::Entity")]
NoteWatching,
#[sea_orm(has_many = "super::page::Entity")]
Page,
#[sea_orm(has_many = "super::page_like::Entity")]
PageLike,
#[sea_orm(has_many = "super::password_reset_request::Entity")]
PasswordResetRequest,
#[sea_orm(has_many = "super::poll_vote::Entity")]
@ -144,14 +136,6 @@ pub enum Relation {
Signin,
#[sea_orm(has_many = "super::sw_subscription::Entity")]
SwSubscription,
#[sea_orm(has_many = "super::user_group::Entity")]
UserGroup,
#[sea_orm(has_many = "super::user_group_invitation::Entity")]
UserGroupInvitation,
#[sea_orm(has_many = "super::user_group_invite::Entity")]
UserGroupInvite,
#[sea_orm(has_many = "super::user_group_joining::Entity")]
UserGroupJoining,
#[sea_orm(has_one = "super::user_keypair::Entity")]
UserKeypair,
#[sea_orm(has_many = "super::user_list::Entity")]
@ -218,18 +202,6 @@ impl Related<super::drive_folder::Entity> for Entity {
}
}
impl Related<super::gallery_like::Entity> for Entity {
fn to() -> RelationDef {
Relation::GalleryLike.def()
}
}
impl Related<super::gallery_post::Entity> for Entity {
fn to() -> RelationDef {
Relation::GalleryPost.def()
}
}
impl Related<super::meta::Entity> for Entity {
fn to() -> RelationDef {
Relation::Meta.def()
@ -284,18 +256,6 @@ impl Related<super::note_watching::Entity> for Entity {
}
}
impl Related<super::page::Entity> for Entity {
fn to() -> RelationDef {
Relation::Page.def()
}
}
impl Related<super::page_like::Entity> for Entity {
fn to() -> RelationDef {
Relation::PageLike.def()
}
}
impl Related<super::password_reset_request::Entity> for Entity {
fn to() -> RelationDef {
Relation::PasswordResetRequest.def()
@ -332,30 +292,6 @@ impl Related<super::sw_subscription::Entity> for Entity {
}
}
impl Related<super::user_group::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroup.def()
}
}
impl Related<super::user_group_invitation::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupInvitation.def()
}
}
impl Related<super::user_group_invite::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupInvite.def()
}
}
impl Related<super::user_group_joining::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserGroupJoining.def()
}
}
impl Related<super::user_keypair::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserKeypair.def()

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.1
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.10
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

Some files were not shown because too many files have changed in this diff Show More