Lots of work
This commit is contained in:
parent
a2a937791d
commit
3086b0e9b4
28 changed files with 1284 additions and 2 deletions
24
ap/checkHeader.go
Normal file
24
ap/checkHeader.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package ap
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// Header used for making requests for AP resources
|
||||||
|
// Not used yet
|
||||||
|
const OUTBOUND_REQUEST_HEADER = "application/ld+json, application/json;q=0.9, application/javascript;q=0.5, text/javascript;q=0.5, text/plain;q=0.2, */*;q=0.1"
|
||||||
|
|
||||||
|
var contentHeadersToCheck = []string{
|
||||||
|
"application/json",
|
||||||
|
"application/ld+json",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a given string if it contains any of the content types specified in
|
||||||
|
// the contentHeadersToCheck slice
|
||||||
|
// Used for differentiating requests for the ActivityPub version of some data vs frontend version
|
||||||
|
func ContainsApContentHeader(toCheck string) bool {
|
||||||
|
for _, h := range contentHeadersToCheck {
|
||||||
|
if strings.Contains(toCheck, h) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
17
ap/common_types.go
Normal file
17
ap/common_types.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package ap
|
||||||
|
|
||||||
|
import "net/url"
|
||||||
|
|
||||||
|
type IdType struct {
|
||||||
|
Id *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValueType[T any] struct {
|
||||||
|
Value T
|
||||||
|
}
|
||||||
|
|
||||||
|
type Media struct {
|
||||||
|
Type url.URL
|
||||||
|
MediaType string
|
||||||
|
Url url.URL
|
||||||
|
}
|
58
ap/constants.go
Normal file
58
ap/constants.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package ap
|
||||||
|
|
||||||
|
const (
|
||||||
|
KEY_ID = "@id" // Value of type string
|
||||||
|
KEY_TYPE = "@type" // Value of type string slice / activitystreams object url slice
|
||||||
|
KEY_VALUE = "@value" // Could be any type really
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KEY_MASTO_DEVICES = "http://joinmastodon.org/ns#devices"
|
||||||
|
KEY_MASTO_DISCOVERABLE = "http://joinmastodon.org/ns#discoverable"
|
||||||
|
KEY_MASTO_FEATURED = "http://joinmastodon.org/ns#featured"
|
||||||
|
KEY_MASTO_FEATURED_TAGS = "http://joinmastodon.org/ns#featuredTags"
|
||||||
|
KEY_MASTO_INDEXABLE = "http://joinmastodon.org/ns#indexable"
|
||||||
|
KEY_MASTO_MEMORIAL = "http://joinmastodon.org/ns#memorial"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KEY_W3_INBOX = "http://www.w3.org/ns/ldp#inbox"
|
||||||
|
KEY_W3_SECURITY_PUBLICKEY = "https://w3id.org/security#publicKey"
|
||||||
|
KEY_W3_SECURITY_OWNER = "https://w3id.org/security#owner"
|
||||||
|
KEY_W3_SECURITY_PUBLICKEYPEM = "https://w3id.org/security#publicKeyPem"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KEY_ACTIVITYSTREAMS_ALSOKNOWNAS = "https://www.w3.org/ns/activitystreams#alsoKnownAs"
|
||||||
|
KEY_ACTIVITYSTREAMS_ATTACHMENTS = "https://www.w3.org/ns/activitystreams#attachment"
|
||||||
|
KEY_ACTIVITYSTREAMS_NAME = "https://www.w3.org/ns/activitystreams#name"
|
||||||
|
KEY_ACTIVITYSTREAMS_ENDPOINTS = "https://www.w3.org/ns/activitystreams#endpoints"
|
||||||
|
KEY_ACTIVITYSTREAMS_SHAREDINBOX = "https://www.w3.org/ns/activitystreams#sharedInbox"
|
||||||
|
KEY_ACTIVITYSTREAMS_FOLLOWERS = "https://www.w3.org/ns/activitystreams#followers"
|
||||||
|
KEY_ACTIVITYSTREAMS_FOLLOWING = "https://www.w3.org/ns/activitystreams#following"
|
||||||
|
KEY_ACTIVITYSTREAMS_ICON = "https://www.w3.org/ns/activitystreams#icon"
|
||||||
|
KEY_ACTIVITYSTREAMS_MEDIATYPE = "https://www.w3.org/ns/activitystreams#mediaType"
|
||||||
|
KEY_ACTIVITYSTREAMS_URL = "https://www.w3.org/ns/activitystreams#url"
|
||||||
|
KEY_ACTIVITYSTREAMS_IMAGE = "https://www.w3.org/ns/activitystreams#image"
|
||||||
|
KEY_ACTIVITYSTREAMS_RESTRICTED_FOLLOW = "https://www.w3.org/ns/activitystreams#manuallyApprovesFollowers"
|
||||||
|
KEY_ACTIVITYSTREAMS_OUTBOX = "https://www.w3.org/ns/activitystreams#outbox"
|
||||||
|
KEY_ACTIVITYSTREAMS_PREFFEREDUSERNAME = "https://www.w3.org/ns/activitystreams#preferredUsername"
|
||||||
|
KEY_ACTIVITYSTREAMS_PUBLISHED = "https://www.w3.org/ns/activitystreams#published"
|
||||||
|
KEY_ACTIVITYSTREAMS_SUMMARY = "https://www.w3.org/ns/activitystreams#summary"
|
||||||
|
KEY_ACTIVITYSTREAMS_TAG = "https://www.w3.org/ns/activitystreams#tag"
|
||||||
|
KEY_ACTIVITYSTREAMS_CC = "https://www.w3.org/ns/activitystreams#cc"
|
||||||
|
KEY_ACTIVITYSTREAMS_TO = "https://www.w3.org/ns/activitystreams#to"
|
||||||
|
KEY_ACTIVITYSTREAMS_OBJECT = "https://www.w3.org/ns/activitystreams#object"
|
||||||
|
|
||||||
|
// Object types (I think)
|
||||||
|
// Those are values the object type can have
|
||||||
|
|
||||||
|
KEY_ACTIVITYSTREAMS_ACTOR = "https://www.w3.org/ns/activitystreams#actor"
|
||||||
|
KEY_ACTIVITYSTREAMS_FOLLOW = "https://www.w3.org/ns/activitystreams#Follow"
|
||||||
|
KEY_ACTIVITYSTREAMS_PERSON = "https://www.w3.org/ns/activitystreams#Person"
|
||||||
|
KEY_ACTIVITYSTREAMS_CREATE = "https://www.w3.org/ns/activitystreams#Create"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KEY_SCHEMA_VALUE = "http://schema.org#value"
|
||||||
|
)
|
49
ap/context.go
Normal file
49
ap/context.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package ap
|
||||||
|
|
||||||
|
// Just steal this one from sharkey
|
||||||
|
// Also use this one for all AP objects for now
|
||||||
|
// Can make a function to extract context from an expanded object later
|
||||||
|
// TODO: Consider if such a function is worth it or if it would hinder learning
|
||||||
|
var allContext = map[string]any{
|
||||||
|
"@context": []any{
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
map[string]any{
|
||||||
|
"fedibird": "http://fedibird.com/ns#",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"schema": "http://schema.org#",
|
||||||
|
"misskey": "https://misskey-hub.net/ns#",
|
||||||
|
"firefish": "https://joinfirefish.org/ns#",
|
||||||
|
"sharkey": "https://joinsharkey.org/ns#",
|
||||||
|
"vcard": "http://www.w3.org/2006/vcard/ns#",
|
||||||
|
|
||||||
|
"Key": "sec:Key",
|
||||||
|
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"quoteUrl": "as:quoteUrl",
|
||||||
|
|
||||||
|
"quoteUri": "fedibird:quoteUri",
|
||||||
|
|
||||||
|
"Emoji": "toot:Emoji",
|
||||||
|
"featured": "toot:featured",
|
||||||
|
"discoverable": "toot:discoverable",
|
||||||
|
|
||||||
|
"PropertyValue": "schema:PropertyValue",
|
||||||
|
"value": "schema:value",
|
||||||
|
|
||||||
|
"_misskey_content": "misskey:_misskey_content",
|
||||||
|
"_misskey_quote": "misskey:_misskey_quote",
|
||||||
|
"_misskey_reaction": "misskey:_misskey_reaction",
|
||||||
|
"_misskey_votes": "misskey:_misskey_votes",
|
||||||
|
"_misskey_summary": "misskey:_misskey_summary",
|
||||||
|
"isCat": "misskey:isCat",
|
||||||
|
|
||||||
|
"speakAsCat": "firefish:speakAsCat",
|
||||||
|
|
||||||
|
"backgroundUrl": "sharkey:backgroundUrl",
|
||||||
|
"listenbrainz": "sharkey:listenbrainz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
185
ap/examples/masto_account.json
Normal file
185
ap/examples/masto_account.json
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron",
|
||||||
|
"@type": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Person"
|
||||||
|
],
|
||||||
|
"http://joinmastodon.org/ns#devices": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron/collections/devices"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"http://joinmastodon.org/ns#discoverable": [
|
||||||
|
{
|
||||||
|
"@value": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"http://joinmastodon.org/ns#featured": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron/collections/featured"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"http://joinmastodon.org/ns#featuredTags": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron/collections/tags"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"http://joinmastodon.org/ns#indexable": [
|
||||||
|
{
|
||||||
|
"@value": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"http://joinmastodon.org/ns#memorial": [
|
||||||
|
{
|
||||||
|
"@value": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"http://www.w3.org/ns/ldp#inbox": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron/inbox"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://w3id.org/security#publicKey": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron#main-key",
|
||||||
|
"https://w3id.org/security#owner": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://w3id.org/security#publicKeyPem": [
|
||||||
|
{
|
||||||
|
"@value": "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvXc4vkECU2/CeuSo1wtn Foim94Ne1jBMYxTZ9wm2YTdJq1oiZKif06I2fOqDzY/4q/S9uccrE9Bkajv1dnkO Vm31QjWlhVpSKynVxEWjVBO5Ienue8gND0xvHIuXf87o61poqjEoepvsQFElA5ym ovljWGSA/jpj7ozygUZhCXtaS2W5AD5tnBQUpcO0lhItYPYTjnmzcc4y2NbJV8hz 2s2G8qKv8fyimE23gY1XrPJg+cRF+g4PqFXujjlJ7MihD9oqtLGxbu7o1cifTn3x BfIdPythWu5b4cujNsB3m3awJjVmx+MHQ9SugkSIYXV0Ina77cTNS0M2PYiH1PFR TwIDAQAB -----END PUBLIC KEY----- "
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#alsoKnownAs": [
|
||||||
|
{
|
||||||
|
"@id": "https://tooting.ai/users/Gargron"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#attachment": [
|
||||||
|
{
|
||||||
|
"@type": [
|
||||||
|
"http://schema.org#PropertyValue"
|
||||||
|
],
|
||||||
|
"http://schema.org#value": [
|
||||||
|
{
|
||||||
|
"@value": "\u003ca href=\"https://www.patreon.com/mastodon\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"\u003e\u003cspan class=\"invisible\"\u003ehttps://www.\u003c/span\u003e\u003cspan class=\"\"\u003epatreon.com/mastodon\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#name": [
|
||||||
|
{
|
||||||
|
"@value": "Patreon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": [
|
||||||
|
"http://schema.org#PropertyValue"
|
||||||
|
],
|
||||||
|
"http://schema.org#value": [
|
||||||
|
{
|
||||||
|
"@value": "\u003ca href=\"https://github.com/Gargron\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003egithub.com/Gargron\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#name": [
|
||||||
|
{
|
||||||
|
"@value": "GitHub"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#endpoints": [
|
||||||
|
{
|
||||||
|
"https://www.w3.org/ns/activitystreams#sharedInbox": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/inbox"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#followers": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron/followers"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#following": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron/following"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#icon": [
|
||||||
|
{
|
||||||
|
"@type": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Image"
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#mediaType": [
|
||||||
|
{
|
||||||
|
"@value": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#url": [
|
||||||
|
{
|
||||||
|
"@id": "https://files.mastodon.social/accounts/avatars/000/000/001/original/a0a49d80c3de5f75.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#image": [
|
||||||
|
{
|
||||||
|
"@type": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Image"
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#mediaType": [
|
||||||
|
{
|
||||||
|
"@value": "image/jpeg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#url": [
|
||||||
|
{
|
||||||
|
"@id": "https://files.mastodon.social/accounts/headers/000/000/001/original/d13e4417706a5fec.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#manuallyApprovesFollowers": [
|
||||||
|
{
|
||||||
|
"@value": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#name": [
|
||||||
|
{
|
||||||
|
"@value": "Eugen Rochko"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#outbox": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/users/Gargron/outbox"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#preferredUsername": [
|
||||||
|
{
|
||||||
|
"@value": "Gargron"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#published": [
|
||||||
|
{
|
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||||
|
"@value": "2016-03-16T00:00:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#summary": [
|
||||||
|
{
|
||||||
|
"@value": "\u003cp\u003eFounder of \u003cspan class=\"h-card\" translate=\"no\"\u003e\u003ca href=\"https://mastodon.social/@Mastodon\" class=\"u-url mention\"\u003e@\u003cspan\u003eMastodon\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e. Film photography, prog metal, Dota 2. Likes all things analog.\u003c/p\u003e"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#tag": [],
|
||||||
|
"https://www.w3.org/ns/activitystreams#url": [
|
||||||
|
{
|
||||||
|
"@id": "https://mastodon.social/@Gargron"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
18
ap/examples/masto_follow_request.json
Normal file
18
ap/examples/masto_follow_request.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"@id": "https://activitypub.academy/1e8a5594-eff7-4946-86fe-84d82d0a14ae",
|
||||||
|
"@type": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Follow"
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#actor": [
|
||||||
|
{
|
||||||
|
"@id": "https://activitypub.academy/users/dadacio_dashdorrol"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#object": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
41
ap/examples/mk_note.json
Normal file
41
ap/examples/mk_note.json
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/notes/9ttp29lhge2u0454",
|
||||||
|
"@type": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Note"
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#attachment": [],
|
||||||
|
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#cc": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/users/9n39zo1rfckr00q5/followers"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#content": [
|
||||||
|
{
|
||||||
|
"@value": "\u003cp\u003eTest post, ignore\u003c/p\u003e"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#published": [
|
||||||
|
{
|
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||||
|
"@value": "2024-05-28T08:22:59.861Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#sensitive": [
|
||||||
|
{
|
||||||
|
"@value": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#tag": [],
|
||||||
|
"https://www.w3.org/ns/activitystreams#to": [
|
||||||
|
{
|
||||||
|
"@id": "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
70
ap/examples/mk_note_create.json
Normal file
70
ap/examples/mk_note_create.json
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/notes/9ttp29lhge2u0454/activity",
|
||||||
|
"@type": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Create"
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#actor": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#cc": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/users/9n39zo1rfckr00q5/followers"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#object": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/notes/9ttp29lhge2u0454",
|
||||||
|
"@type": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Note"
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#attachment": [],
|
||||||
|
"https://www.w3.org/ns/activitystreams#attributedTo": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/users/9n39zo1rfckr00q5"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#cc": [
|
||||||
|
{
|
||||||
|
"@id": "https://woem.men/users/9n39zo1rfckr00q5/followers"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#content": [
|
||||||
|
{
|
||||||
|
"@value": "\u003cp\u003eTest post, ignore\u003c/p\u003e"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#published": [
|
||||||
|
{
|
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||||
|
"@value": "2024-05-28T08:22:59.861Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#sensitive": [
|
||||||
|
{
|
||||||
|
"@value": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#tag": [],
|
||||||
|
"https://www.w3.org/ns/activitystreams#to": [
|
||||||
|
{
|
||||||
|
"@id": "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#published": [
|
||||||
|
{
|
||||||
|
"@type": "http://www.w3.org/2001/XMLSchema#dateTime",
|
||||||
|
"@value": "2024-05-28T08:22:59.861Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"https://www.w3.org/ns/activitystreams#to": [
|
||||||
|
{
|
||||||
|
"@id": "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
62
ap/explicit_types.go
Normal file
62
ap/explicit_types.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package ap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ActivityStreamsAlsoKnownAs IdType
|
||||||
|
ActivityStreamsAttachment struct {
|
||||||
|
Type *url.URL
|
||||||
|
Value string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
ActivityStreamsSharedInbox IdType
|
||||||
|
ActivityStreamsFollowers IdType
|
||||||
|
ActivityStreamsFollowing IdType
|
||||||
|
ActivityStreamsImage Media
|
||||||
|
ActivityStreamsIcon Media
|
||||||
|
ActivityStreamsRestrictedFollow ValueType[bool]
|
||||||
|
ActivityStreamsName ValueType[string]
|
||||||
|
ActivityStreamsOutbox IdType
|
||||||
|
ActivityStreamsPrefferedUsername ValueType[string]
|
||||||
|
ActivityStreamsPublished struct {
|
||||||
|
Type string
|
||||||
|
Value time.Time
|
||||||
|
}
|
||||||
|
ActivityStreamsSummary ValueType[string]
|
||||||
|
ActivityStreamsUrl IdType
|
||||||
|
// NOTE: I do not know if this is consistent at all. Do not trust yet
|
||||||
|
ActivityStreamsTag struct {
|
||||||
|
Type string
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
ActivityStreamsTo IdType
|
||||||
|
ActivityStreamsCC IdType
|
||||||
|
|
||||||
|
ActivityStreamsActor IdType
|
||||||
|
// NOTE: Technically, objects can have a LOT of data. I don't care. Treat them as ID type
|
||||||
|
// Just fetch whatever the ID is later on separately and throw away anything else but the ID
|
||||||
|
ActivityStreamsObject IdType
|
||||||
|
ActivityStreamsFollow IdType
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
MastoDevices IdType
|
||||||
|
MastoDiscoverable ValueType[bool]
|
||||||
|
MastoFeatured IdType
|
||||||
|
MastoFeaturedTags IdType
|
||||||
|
MastoIndexable ValueType[bool]
|
||||||
|
MastoMemorial ValueType[bool]
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
W3Inbox IdType
|
||||||
|
W3SecurityPublicKey struct {
|
||||||
|
Id *url.URL
|
||||||
|
Owner *url.URL
|
||||||
|
KeyPem string
|
||||||
|
}
|
||||||
|
)
|
59
ap/parser.go
Normal file
59
ap/parser.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package ap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/piprate/json-gold/ld"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApThing map[string]any
|
||||||
|
|
||||||
|
type RemoteDocumentLoader struct{}
|
||||||
|
|
||||||
|
// Try and parse a remote ActivityPub object into a more usable form
|
||||||
|
// Result is a map[string]any where the keys defined in /ap/constants.go should be usable,
|
||||||
|
// depending on the type of object
|
||||||
|
// The general approach for fetching an object will be to fetch the main object
|
||||||
|
// and only store the ID for sub-objects to fetch them later
|
||||||
|
func ParseFromUrl(u *url.URL) (ApThing, error) {
|
||||||
|
opts := ld.NewJsonLdOptions("")
|
||||||
|
processor := ld.NewJsonLdProcessor()
|
||||||
|
// TODO: Add custom document parser (copy default implementation) that includes verification
|
||||||
|
|
||||||
|
// Explanation:
|
||||||
|
// Expansion removes the context from a document (json-ld activitypub data)
|
||||||
|
// and turns every field into something along the lines of
|
||||||
|
// "https://example.com/ns#ObjectType": <Insert data for that object here>
|
||||||
|
// This makes it easier to deal with things as they now have a very consistent naming scheme
|
||||||
|
// See /ap/constants.go for those
|
||||||
|
|
||||||
|
parsed, err := processor.Expand(u.String(), opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to process remote document: %w", err)
|
||||||
|
}
|
||||||
|
if len(parsed) == 0 {
|
||||||
|
return nil, fmt.Errorf("document has a length of 0")
|
||||||
|
}
|
||||||
|
typed, ok := parsed[0].(ApThing)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("couldn't cast data to ApThing")
|
||||||
|
}
|
||||||
|
return typed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact an AP object down into a compressed json-ld form
|
||||||
|
// That compacted form should be accepted by all AP servers
|
||||||
|
// It also handles context for any fields
|
||||||
|
// Content should only use keys defined in /ap/constants.go though
|
||||||
|
// Other things might get lost in translation
|
||||||
|
func Compact(content map[string]any) ([]byte, error) {
|
||||||
|
opts := ld.NewJsonLdOptions("")
|
||||||
|
processor := ld.NewJsonLdProcessor()
|
||||||
|
res, err := processor.Compact(content, allContext, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compact data: %w", err)
|
||||||
|
}
|
||||||
|
return json.Marshal(res)
|
||||||
|
}
|
172
ap/type_parsers.go
Normal file
172
ap/type_parsers.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
package ap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Try and parse a value into an IdType
|
||||||
|
// Returns nil if failed
|
||||||
|
func TryParseIdType(rawIn any) *IdType {
|
||||||
|
switch in := rawIn.(type) {
|
||||||
|
case []any:
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m, ok := in[0].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return TryParseIdType(m)
|
||||||
|
case map[string]any:
|
||||||
|
vRaw, ok := in[KEY_ID]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v, ok := vRaw.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
u, err := url.Parse(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &IdType{
|
||||||
|
Id: u,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryParseValueType[T any](rawIn any) *ValueType[T] {
|
||||||
|
switch in := rawIn.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
vRaw, ok := in[KEY_ID]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v, ok := vRaw.(T)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &ValueType[T]{
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v, ok := in[0].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return TryParseValueType[T](v)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryParseActivityStreamsPublicKey(rawIn any) *W3SecurityPublicKey {
|
||||||
|
switch in := rawIn.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
asIdType := TryParseIdType(in)
|
||||||
|
if asIdType == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ownerType := TryParseIdType(in[KEY_W3_SECURITY_OWNER])
|
||||||
|
if ownerType == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keyValue := TryParseValueType[string](in[KEY_W3_SECURITY_PUBLICKEYPEM])
|
||||||
|
if keyValue == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &W3SecurityPublicKey{
|
||||||
|
Id: asIdType.Id,
|
||||||
|
Owner: ownerType.Id,
|
||||||
|
KeyPem: keyValue.Value,
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return TryParseActivityStreamsPublicKey(in[0])
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryParseActivityStreamsAttachment(rawIn any) *ActivityStreamsAttachment {
|
||||||
|
switch in := rawIn.(type) {
|
||||||
|
case []any:
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return TryParseActivityStreamsAttachment(in[0])
|
||||||
|
case map[string]any:
|
||||||
|
rawType, ok := in[KEY_TYPE]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
strType, ok := rawType.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
urlType, err := url.Parse(strType)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
value := TryParseValueType[string](in[KEY_SCHEMA_VALUE])
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := TryParseValueType[string](in[KEY_ACTIVITYSTREAMS_NAME])
|
||||||
|
if name == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &ActivityStreamsAttachment{
|
||||||
|
Type: urlType,
|
||||||
|
Name: name.Value,
|
||||||
|
Value: value.Value,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TryParseActivityStreamsPublished(rawIn any) *ActivityStreamsPublished {
|
||||||
|
switch in := rawIn.(type) {
|
||||||
|
case []any:
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return TryParseActivityStreamsPublished(in[0])
|
||||||
|
case map[string]any:
|
||||||
|
rawType, ok := in[KEY_TYPE]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
strType, ok := rawType.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
value := TryParseValueType[string](in)
|
||||||
|
tv, err := time.Parse("2006-01-02T04:05:06Z", value.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &ActivityStreamsPublished{
|
||||||
|
Type: strType,
|
||||||
|
Value: tv,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Since I do not know if tags are consistent with the struct yet,
|
||||||
|
// This funtion does not do anything yet and should not be used
|
||||||
|
func TryParseActivityStreamsTag(rawIn any) *ActivityStreamsTag {
|
||||||
|
return nil
|
||||||
|
}
|
35
go.mod
35
go.mod
|
@ -3,6 +3,37 @@ module gitlab.com/mstarongitlab/linstrom
|
||||||
go 1.22.2
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/piprate/json-gold v0.5.0 // indirect
|
github.com/piprate/json-gold v0.5.0
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
gorm.io/gorm v1.25.10
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
|
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||||
|
github.com/go-webauthn/webauthn v0.10.2 // indirect
|
||||||
|
github.com/go-webauthn/x v0.1.9 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
github.com/google/go-tpm v0.9.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
gorm.io/driver/postgres v1.5.7 // indirect
|
||||||
|
modernc.org/libc v1.22.5 // indirect
|
||||||
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
modernc.org/sqlite v1.23.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
78
go.sum
78
go.sum
|
@ -1,4 +1,82 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
|
||||||
|
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
|
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||||
|
github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4=
|
||||||
|
github.com/go-webauthn/webauthn v0.10.2/go.mod h1:Gd1IDsGAybuvK1NkwUTLbGmeksxuRJjVN2PE/xsPxHs=
|
||||||
|
github.com/go-webauthn/x v0.1.9 h1:v1oeLmoaa+gPOaZqUdDentu6Rl7HkSSsmOT6gxEQHhE=
|
||||||
|
github.com/go-webauthn/x v0.1.9/go.mod h1:pJNMlIMP1SU7cN8HNlKJpLEnFHCygLCvaLZ8a1xeoQA=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||||
|
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/piprate/json-gold v0.5.0 h1:RmGh1PYboCFcchVFuh2pbSWAZy4XJaqTMU4KQYsApbM=
|
github.com/piprate/json-gold v0.5.0 h1:RmGh1PYboCFcchVFuh2pbSWAZy4XJaqTMU4KQYsApbM=
|
||||||
github.com/piprate/json-gold v0.5.0/go.mod h1:WZ501QQMbZZ+3pXFPhQKzNwS1+jls0oqov3uQ2WasLs=
|
github.com/piprate/json-gold v0.5.0/go.mod h1:WZ501QQMbZZ+3pXFPhQKzNwS1+jls0oqov3uQ2WasLs=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||||
|
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||||
|
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||||
|
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
|
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||||
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||||
|
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
|
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||||
|
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||||
|
|
36
server/endpoints_ap.go
Normal file
36
server/endpoints_ap.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mount under /.well-known/webfinger
|
||||||
|
func webfingerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logEntry, ok := r.Context().Value(middlewares.CONTEXT_KEY_LOGRUS).(*logrus.Entry)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "couldn't get logging entry from context", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
store := storage.Storage{}
|
||||||
|
|
||||||
|
requestedResource := r.FormValue("resource")
|
||||||
|
if requestedResource == "" {
|
||||||
|
http.Error(w, "bad request. Include \"resource\" parameter", http.StatusBadRequest)
|
||||||
|
logEntry.Infoln("No resource parameter. Cancelling")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accName := strings.TrimPrefix(requestedResource, "acc:")
|
||||||
|
acc, err := store.FindLocalAccount(accName)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
logEntry.WithError(err).Warningln("couldn't find account")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, acc)
|
||||||
|
}
|
20
server/middlewares/injectLogrus.go
Normal file
20
server/middlewares/injectLogrus.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CONTEXT_KEY_LOGRUS = ContextKey("logrus")
|
||||||
|
|
||||||
|
// Inject a logrus entry into the context that has the url path already set
|
||||||
|
func InjectLogrusMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqContext := r.Context()
|
||||||
|
entry := logrus.WithField("url-path", r.URL.Path)
|
||||||
|
newContext := context.WithValue(reqContext, CONTEXT_KEY_LOGRUS, entry)
|
||||||
|
next.ServeHTTP(w, r.WithContext(newContext))
|
||||||
|
})
|
||||||
|
}
|
22
server/middlewares/injectStorage.go
Normal file
22
server/middlewares/injectStorage.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CONTEXT_KEY_STORAGE = ContextKey("storage")
|
||||||
|
|
||||||
|
// Build a middleware for injecting a storage reference into the context
|
||||||
|
func InjectStorageMiddlewareBuilder(store *storage.Storage) func(http.Handler) http.Handler {
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqContext := r.Context()
|
||||||
|
newContext := context.WithValue(reqContext, CONTEXT_KEY_STORAGE, store)
|
||||||
|
newRequest := r.WithContext(newContext)
|
||||||
|
h.ServeHTTP(w, newRequest)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
14
server/middlewares/middleswares.go
Normal file
14
server/middlewares/middleswares.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type ContextKey string
|
||||||
|
type MiddlewareFunc func(http.Handler) http.Handler
|
||||||
|
|
||||||
|
func ChainMiddlewares(start http.Handler, middlewares ...MiddlewareFunc) http.Handler {
|
||||||
|
next := start
|
||||||
|
for _, h := range middlewares {
|
||||||
|
next = h(next)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
23
server/notes.go
Normal file
23
server/notes.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/ap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mount at /notes/{note-id}
|
||||||
|
// Handles the note endpoint
|
||||||
|
// Serves the json-ld representation of a note OR the frontend view
|
||||||
|
func noteHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if ap.ContainsApContentHeader(r.Header.Get("Content-Type")) {
|
||||||
|
apNote(w, r)
|
||||||
|
} else {
|
||||||
|
renderNote(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderNote(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "not implemented yet", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
func apNote(w http.ResponseWriter, r *http.Request) {}
|
27
server/server.go
Normal file
27
server/server.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
|
||||||
|
"gitlab.com/mstarongitlab/linstrom/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(store *storage.Storage) *Server {
|
||||||
|
handler := http.NewServeMux()
|
||||||
|
handler.HandleFunc("/.well-known/webfinger", webfingerHandler)
|
||||||
|
// handler.Handle("/api/", http.StripPrefix("/ap", buildApiRouter()))
|
||||||
|
|
||||||
|
withMiddlewares := middlewares.ChainMiddlewares(
|
||||||
|
handler,
|
||||||
|
middlewares.InjectLogrusMiddleware,
|
||||||
|
middlewares.InjectStorageMiddlewareBuilder(store),
|
||||||
|
)
|
||||||
|
return &Server{
|
||||||
|
handler: withMiddlewares,
|
||||||
|
}
|
||||||
|
}
|
22
storage/mediaFile.go
Normal file
22
storage/mediaFile.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MediaFile struct {
|
||||||
|
ID string `gorm:"primarykey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
Remote bool // whether the attachment is a remote one
|
||||||
|
Link string // url if remote attachment, identifier if local
|
||||||
|
// Whether this media has been cached locally
|
||||||
|
// Only really used for user and server icons, not attachments
|
||||||
|
// Reason: Attachments would take way to much space considering that they are often only loaded a few times at most
|
||||||
|
// And caching a file for those few times would be a waste of storage
|
||||||
|
// Caching user and server icons locally however should reduce burden on remote servers by quite a bit though
|
||||||
|
LocallyCached bool
|
||||||
|
}
|
28
storage/noteTargets.go
Normal file
28
storage/noteTargets.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NoteTarget uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
NOTE_TARGET_PUBLIC = NoteTarget(0)
|
||||||
|
NOTE_TARGET_HOME = NoteTarget(1 << iota)
|
||||||
|
NOTE_TARGET_FOLLOWERS
|
||||||
|
NOTE_TARGET_DM
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *NoteTarget) Value() (driver.Value, error) {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NoteTarget) Scan(value any) error {
|
||||||
|
vBig, ok := value.(int64)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an int64")
|
||||||
|
}
|
||||||
|
*n = NoteTarget(vBig)
|
||||||
|
return nil
|
||||||
|
}
|
28
storage/notes.go
Normal file
28
storage/notes.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Note struct {
|
||||||
|
ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
Creator string // Full handle of the creator, eg: @max@example.com
|
||||||
|
Remote bool // Whether the note is originally a remote one and just "cached"
|
||||||
|
// Raw content of the note. So without additional formatting applied
|
||||||
|
// Might already have formatting applied beforehand from the origin server
|
||||||
|
RawContent string
|
||||||
|
ContentWarning *string // Content warnings of the note, if it contains any
|
||||||
|
Attachments []string `gorm:"serializer:json"` // Links to attachments
|
||||||
|
Emotes []string `gorm:"serializer:json"` // Emotes used in that message
|
||||||
|
RepliesTo *string // Url of the message this replies to
|
||||||
|
Quotes *string // url of the message this note quotes
|
||||||
|
Target NoteTarget // Where to send this message to (public, home, followers, dm)
|
||||||
|
Pings []string `gorm:"serializer:json"` // Who is being tagged in this message. Also serves as DM targets
|
||||||
|
OriginServer string // Url of the origin server. Also the primary key for those
|
||||||
|
Tags []string `gorm:"serializer:json"` // Hashtags
|
||||||
|
}
|
18
storage/remoteServerInfo.go
Normal file
18
storage/remoteServerInfo.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RemoteServer struct {
|
||||||
|
ID string `gorm:"primarykey"` // ID is also server url
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
ServerType RemoteServerType // What software the server is running. Useful for formatting
|
||||||
|
Name string // What the server wants to be known as (usually same as url)
|
||||||
|
Icon string // ID of a media file
|
||||||
|
IsSelf bool // Whether this server is yours truly
|
||||||
|
}
|
33
storage/serverTypes.go
Normal file
33
storage/serverTypes.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mostly important for rendering
|
||||||
|
type RemoteServerType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Includes forks like glitch-soc, etc
|
||||||
|
REMOTE_SERVER_MASTODON = RemoteServerType("Mastodon")
|
||||||
|
// Includes forks like Ice Shrimp, Sharkey, Cutiekey, etc
|
||||||
|
REMOTE_SERVER_MISSKEY = RemoteServerType("Misskey")
|
||||||
|
// Includes Akkoma
|
||||||
|
REMOTE_SERVER_PLEMORA = RemoteServerType("Plemora")
|
||||||
|
// And of course, yours truly
|
||||||
|
REMOTE_SERVER_LINSTROM = RemoteServerType("Linstrom")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *RemoteServerType) Value() (driver.Value, error) {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RemoteServerType) Scan(raw any) error {
|
||||||
|
if v, ok := raw.(string); ok {
|
||||||
|
*r = RemoteServerType(v)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("value not a string")
|
||||||
|
}
|
||||||
|
}
|
36
storage/storage.go
Normal file
36
storage/storage.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStorageSqlite(filePath string) (*Storage, error) {
|
||||||
|
db, err := gorm.Open(sqlite.Open(filePath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Storage{
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStoragePostgres(dbUrl string) (*Storage, error) {
|
||||||
|
db, err := gorm.Open(postgres.Open(dbUrl))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Storage{
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Placeholder. Update to proper implementation later. Including signature
|
||||||
|
func (s *Storage) FindLocalAccount(handle string) (string, error) {
|
||||||
|
return handle, nil
|
||||||
|
}
|
67
storage/user.go
Normal file
67
storage/user.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Database representation of a user account
|
||||||
|
// This can be a bot, remote or not
|
||||||
|
// If remote, this is used for caching the account
|
||||||
|
type User struct {
|
||||||
|
ID string `gorm:"primarykey"` // ID is the full handle, eg @max@example.com
|
||||||
|
CreatedAt time.Time // When this entry was created
|
||||||
|
UpdatedAt time.Time // When this account was last updated. Will also be used for refreshing remote accounts
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
Remote bool // Whether the account is a local or remote one
|
||||||
|
Server string // The url of the server this account is from
|
||||||
|
DisplayName string // The display name of the user. Can be different from the handle
|
||||||
|
CustomFields []uint `gorm:"serializer:json"` // IDs to the custom fields a user has
|
||||||
|
Description string // The description of a user account
|
||||||
|
Tags []string `gorm:"serializer:json"` // Hashtags
|
||||||
|
IsBot bool // Whether to mark this account as a script controlled one
|
||||||
|
Follows []string `gorm:"serializer:json"` // List of handles this account follows
|
||||||
|
Followers []string `gorm:"serializer:json"` // List of handles that follow this account
|
||||||
|
Icon string // ID of a media file used as icon
|
||||||
|
Background string // ID of a media file used as background image
|
||||||
|
Banner string // ID of a media file used as banner
|
||||||
|
Indexable bool // Whether this account can be found by crawlers
|
||||||
|
PublicKeyPem string // The public key of the account
|
||||||
|
// Whether this account restricts following
|
||||||
|
// If true, the owner must approve of a follow request first
|
||||||
|
RestrictedFollow bool
|
||||||
|
// List of things the owner identifies as
|
||||||
|
// Example [cat human robot] means that the owner probably identifies as
|
||||||
|
// a cyborg-catgirl/boy/human
|
||||||
|
IdentifiesAs []Being
|
||||||
|
// List of pronouns the owner identifies with
|
||||||
|
// An unordered list since the owner can freely set it
|
||||||
|
// Examples: [she her], [it they its them]
|
||||||
|
Gender []string
|
||||||
|
|
||||||
|
// --- And internal account stuff ---
|
||||||
|
// Still public fields since they wouldn't be able to be stored in the db otherwise
|
||||||
|
|
||||||
|
PasswordHash []byte // Hash of the user's password
|
||||||
|
TotpToken []byte // Token for totp verification
|
||||||
|
// All the registered passkeys, name of passkey to credentials
|
||||||
|
// Could this be exported to another table? Yes
|
||||||
|
// Would it make sense? Probably not
|
||||||
|
// Will just take the performance hit of json conversion
|
||||||
|
// Access should be rare enough anyway
|
||||||
|
Passkeys map[string]webauthn.Credential `gorm:"serializer:json"`
|
||||||
|
PrivateKeyPem *string // The private key of the account. Nil if remote user
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEmptyUser() *User {
|
||||||
|
return &User{
|
||||||
|
ID: uuid.NewString(),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
Remote: false,
|
||||||
|
Server: "placeholder",
|
||||||
|
}
|
||||||
|
}
|
30
storage/userIdentType.go
Normal file
30
storage/userIdentType.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Being string
|
||||||
|
|
||||||
|
const (
|
||||||
|
BEING_HUMAN = Being("human")
|
||||||
|
BEING_CAT = Being("cat")
|
||||||
|
BEING_FOX = Being("fox")
|
||||||
|
BEING_DOG = Being("dog")
|
||||||
|
BEING_ROBOT = Being("robot")
|
||||||
|
BEING_DOLL = Being("doll")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Being) Value() (driver.Value, error) {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Being) Scan(raw any) error {
|
||||||
|
if v, ok := raw.(string); ok {
|
||||||
|
*r = Being(v)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("value not a string")
|
||||||
|
}
|
||||||
|
}
|
14
storage/userInfoFields.go
Normal file
14
storage/userInfoFields.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserInfoField struct {
|
||||||
|
gorm.Model // Can actually just embed this as is here as those are not something directly exposed :3
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
LastUrlCheckDate *time.Time // Used if the value is an url to somewhere. Empty if value is not an url
|
||||||
|
}
|
Loading…
Reference in a new issue