From 94197780e1694ac2250b0dc1007fb64f85c1f6f7 Mon Sep 17 00:00:00 2001 From: mStar aka a person <12024604-mstarongitlab@users.noreply.gitlab.com> Date: Thu, 6 Jun 2024 11:54:50 +0000 Subject: [PATCH 01/13] Add comments to storage types --- storage/mediaFile.go | 9 ++++++--- storage/noteTargets.go | 9 +++++++++ storage/notes.go | 9 ++++++--- storage/remoteServerInfo.go | 7 +++++-- storage/serverTypes.go | 1 + storage/user.go | 3 +++ storage/userIdentType.go | 1 + 7 files changed, 31 insertions(+), 8 deletions(-) diff --git a/storage/mediaFile.go b/storage/mediaFile.go index 5ec406e..6edd1fa 100644 --- a/storage/mediaFile.go +++ b/storage/mediaFile.go @@ -7,9 +7,12 @@ import ( ) type MediaFile struct { - ID string `gorm:"primarykey"` - CreatedAt time.Time - UpdatedAt time.Time + ID string `gorm:"primarykey"` // The unique ID of this media file + CreatedAt time.Time // When this entry was created + UpdatedAt time.Time // When this entry was last updated + // When this entry was deleted (for soft deletions) + // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to + // If not null, this entry is marked as deleted DeletedAt gorm.DeletedAt `gorm:"index"` Remote bool // whether the attachment is a remote one Link string // url if remote attachment, identifier if local diff --git a/storage/noteTargets.go b/storage/noteTargets.go index af0ed8f..50b8abf 100644 --- a/storage/noteTargets.go +++ b/storage/noteTargets.go @@ -5,19 +5,28 @@ import ( "errors" ) +// What feed a note is targeting (public, home, followers or dm) type NoteTarget uint8 const ( + // The note is intended for the public NOTE_TARGET_PUBLIC = NoteTarget(0) + // The note is intended only for the home screen + // not really any idea what the difference is compared to public + // Maybe home notes don't show up on the server feed but still for everyone's home feed if it reaches them via follow or boost NOTE_TARGET_HOME = NoteTarget(1 << iota) + // The note is intended only for followers NOTE_TARGET_FOLLOWERS + // The note is intended only for a DM to one or more targets NOTE_TARGET_DM ) +// Converts the NoteTarget value into a type the DB can use func (n *NoteTarget) Value() (driver.Value, error) { return n, nil } +// Converts the raw value from the DB into a NoteTarget func (n *NoteTarget) Scan(value any) error { vBig, ok := value.(int64) if !ok { diff --git a/storage/notes.go b/storage/notes.go index e8011eb..930ec7f 100644 --- a/storage/notes.go +++ b/storage/notes.go @@ -7,9 +7,12 @@ import ( ) type Note struct { - ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations - CreatedAt time.Time - UpdatedAt time.Time + ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations + CreatedAt time.Time // When this entry was created + UpdatedAt time.Time // When this entry was last updated + // When this entry was deleted (for soft deletions) + // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to + // If not null, this entry is marked as deleted 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" diff --git a/storage/remoteServerInfo.go b/storage/remoteServerInfo.go index ab2791b..395b97a 100644 --- a/storage/remoteServerInfo.go +++ b/storage/remoteServerInfo.go @@ -8,8 +8,11 @@ import ( type RemoteServer struct { ID string `gorm:"primarykey"` // ID is also server url - CreatedAt time.Time - UpdatedAt time.Time + CreatedAt time.Time // When this entry was created + UpdatedAt time.Time // When this entry was last updated + // When this entry was deleted (for soft deletions) + // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to + // If not null, this entry is marked as deleted 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) diff --git a/storage/serverTypes.go b/storage/serverTypes.go index 5c5b1f2..111dd85 100644 --- a/storage/serverTypes.go +++ b/storage/serverTypes.go @@ -5,6 +5,7 @@ import ( "errors" ) +// What software a server is running // Mostly important for rendering type RemoteServerType string diff --git a/storage/user.go b/storage/user.go index 73348e0..fed4001 100644 --- a/storage/user.go +++ b/storage/user.go @@ -16,6 +16,9 @@ type User struct { Handle string // Handle 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 + // When this entry was deleted (for soft deletions) + // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to + // If not null, this entry is marked as deleted 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 diff --git a/storage/userIdentType.go b/storage/userIdentType.go index 8d94d61..7ce7df5 100644 --- a/storage/userIdentType.go +++ b/storage/userIdentType.go @@ -5,6 +5,7 @@ import ( "errors" ) +// What kind of being a user identifies as type Being string const ( From 2977f09245b31a6731d6d44fb00000c073b3cf12 Mon Sep 17 00:00:00 2001 From: Melody Date: Wed, 28 Aug 2024 17:20:38 +0200 Subject: [PATCH 02/13] Sync --- .gitignore | 3 + ap/common_types.go | 17 -- ap/constants.go | 58 ------ ap/context.go | 49 ----- ap/examples/masto_account.json | 185 ----------------- ap/examples/masto_follow_request.json | 18 -- ap/examples/mk_note.json | 41 ---- ap/examples/mk_note_create.json | 70 ------- ap/explicit_types.go | 62 ------ ap/getRemoteUser.go | 27 +++ ap/parser.go | 59 ------ ap/type_parsers.go | 172 ---------------- ap/util.go | 19 ++ ap/webfinger.go | 87 ++++++++ config/config.go | 155 +++++++++++++- flags.go | 25 +++ frontend | 1 + go.mod | 41 ++-- go.sum | 82 +++++--- main.go | 51 +++++ server/endpoints_ap.go | 26 +-- server/middlewares/injectLogrus.go | 7 +- storage/errors.go | 7 + storage/mediaFile.go | 15 +- storage/notes.go | 22 +- storage/passkeySessions.go | 45 +++++ storage/remoteServerInfo.go | 14 +- storage/remoteUser.go | 5 + storage/roles.go | 8 + storage/storage.go | 32 +-- storage/user.go | 278 +++++++++++++++++++++----- storage/userIdentType.go | 18 -- 32 files changed, 763 insertions(+), 936 deletions(-) delete mode 100644 ap/common_types.go delete mode 100644 ap/constants.go delete mode 100644 ap/context.go delete mode 100644 ap/examples/masto_account.json delete mode 100644 ap/examples/masto_follow_request.json delete mode 100644 ap/examples/mk_note.json delete mode 100644 ap/examples/mk_note_create.json delete mode 100644 ap/explicit_types.go create mode 100644 ap/getRemoteUser.go delete mode 100644 ap/parser.go delete mode 100644 ap/type_parsers.go create mode 100644 ap/util.go create mode 100644 ap/webfinger.go create mode 100644 flags.go create mode 160000 frontend create mode 100644 main.go create mode 100644 storage/errors.go create mode 100644 storage/passkeySessions.go create mode 100644 storage/remoteUser.go create mode 100644 storage/roles.go diff --git a/.gitignore b/.gitignore index 2ba383b..c1300c8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ db.sqlite .env .lapce/ */tmp.* + +config.toml +.air.toml diff --git a/ap/common_types.go b/ap/common_types.go deleted file mode 100644 index 0d70934..0000000 --- a/ap/common_types.go +++ /dev/null @@ -1,17 +0,0 @@ -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 -} diff --git a/ap/constants.go b/ap/constants.go deleted file mode 100644 index 9e25637..0000000 --- a/ap/constants.go +++ /dev/null @@ -1,58 +0,0 @@ -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" -) diff --git a/ap/context.go b/ap/context.go deleted file mode 100644 index 71f4a4f..0000000 --- a/ap/context.go +++ /dev/null @@ -1,49 +0,0 @@ -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", - }, - }, -} diff --git a/ap/examples/masto_account.json b/ap/examples/masto_account.json deleted file mode 100644 index 3f739b0..0000000 --- a/ap/examples/masto_account.json +++ /dev/null @@ -1,185 +0,0 @@ -[ - { - "@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" - } - ] - } -] diff --git a/ap/examples/masto_follow_request.json b/ap/examples/masto_follow_request.json deleted file mode 100644 index d483b73..0000000 --- a/ap/examples/masto_follow_request.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "@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" - } - ] - } -] diff --git a/ap/examples/mk_note.json b/ap/examples/mk_note.json deleted file mode 100644 index 7376e7d..0000000 --- a/ap/examples/mk_note.json +++ /dev/null @@ -1,41 +0,0 @@ -[ - { - "@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" - } - ] - } -] diff --git a/ap/examples/mk_note_create.json b/ap/examples/mk_note_create.json deleted file mode 100644 index fce13ff..0000000 --- a/ap/examples/mk_note_create.json +++ /dev/null @@ -1,70 +0,0 @@ -[ - { - "@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" - } - ] - } -] diff --git a/ap/explicit_types.go b/ap/explicit_types.go deleted file mode 100644 index 0617363..0000000 --- a/ap/explicit_types.go +++ /dev/null @@ -1,62 +0,0 @@ -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 - } -) diff --git a/ap/getRemoteUser.go b/ap/getRemoteUser.go new file mode 100644 index 0000000..6876088 --- /dev/null +++ b/ap/getRemoteUser.go @@ -0,0 +1,27 @@ +package ap + +import ( + "net/http" + + "gitlab.com/mstarongitlab/goap" +) + +func GetRemoteUser(fullHandle string) (goap.BaseApChain, error) { + webfinger, err := GetAccountWebfinger(fullHandle) + if err != nil { + return nil, err + } + + apUrl := "" + for _, link := range webfinger.Links { + if link.Relation == "self" { + apUrl = *link.Href + } + } + apRequest, err := http.NewRequest("GET", apUrl, nil) + if err != nil { + return nil, err + } + apRequest.Header.Add("Accept", "application/activity+json,application/ld+json,application/json") + return nil, nil +} diff --git a/ap/parser.go b/ap/parser.go deleted file mode 100644 index f91d631..0000000 --- a/ap/parser.go +++ /dev/null @@ -1,59 +0,0 @@ -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": - // 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) -} diff --git a/ap/type_parsers.go b/ap/type_parsers.go deleted file mode 100644 index 451f099..0000000 --- a/ap/type_parsers.go +++ /dev/null @@ -1,172 +0,0 @@ -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 -} diff --git a/ap/util.go b/ap/util.go new file mode 100644 index 0000000..b24a2a1 --- /dev/null +++ b/ap/util.go @@ -0,0 +1,19 @@ +package ap + +import "strings" + +type InvalidFullHandleError struct { + Raw string +} + +func (i InvalidFullHandleError) Error() string { + return "Invalid full handle" +} + +func SplitFullHandle(full string) (string, string, error) { + splits := strings.Split(strings.TrimPrefix(full, "@"), "@") + if len(splits) != 2 { + return "", "", InvalidFullHandleError{} + } + return splits[0], splits[1], nil +} diff --git a/ap/webfinger.go b/ap/webfinger.go new file mode 100644 index 0000000..4d15c02 --- /dev/null +++ b/ap/webfinger.go @@ -0,0 +1,87 @@ +package ap + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "time" +) + +// Data returned from a webfinger response (and also sent when asked for an account via webfinger) +type WebfingerData struct { + // What this webfinger data refers to. Accounts are usually `acct:username@host` + Subject string `json:"subject"` + // List of links related to the account + Links []struct { + // What type of link this is + // - `self` refers to the activitypub object and has Type and Href set + // - `http://webfinger.net/rel/profile-page` refers to the public webpage of the account and has Type and Href set + // - `http://ostatus.org/schema/1.0/subscribe` provides a template for subscribing/following the account. Has Template set + // Template will contain a `{uri}` part with which to replace idk yet + Relation string `json:"rel"` + // The content type of the url + Type *string `json:"type"` + // The url + Href *string `json:"href"` + // Template to use for something + Template *string `json:"template"` + } `json:"links"` +} + +var ErrAccountNotFound = errors.New("account not found") +var ErrRemoteServerFailed = errors.New("remote server errored out") + +// Get the webfinger data for a given full account handle (like @bob@example.com or bob@example.com) +func GetAccountWebfinger(fullHandle string) (*WebfingerData, error) { + // First get the handle and server domain from the full handle (and ensure it's a correctly formatted one) + handle, server, err := SplitFullHandle(fullHandle) + if err != nil { + return nil, err + } + webfingerRequest, err := http.NewRequest( + // Webfinger requests are GET + "GET", + // The webfinger url is located at /.well-known/webfinger + // The url parameter `resource` tells the remote server what data we want, should always be an account + // The prefix `acct:` (where full-handle is in the form of bob@example.com) + // tells the remote server that we want the account data of the given account handle + "https://"+server+"/.well-known/webfinger?resource=acct:"+handle+"@"+server, + // No request body since it's a get request and we only need the url parameter + nil, + ) + if err != nil { + return nil, err + } + // Make a http client with a timeout limit of 30 seconds + client := http.Client{Timeout: time.Second * 30} + // Then send the request + result, err := client.Do(webfingerRequest) + if err != nil { + return nil, err + } + + // Code 404 indicates that the account doesn't exist + if result.StatusCode == 404 { + return nil, ErrAccountNotFound + } else if result.StatusCode != 200 { + // And anything other than 200 or 404 is an error for now + // TODO: Add information gathering here to figure out what the remote server's problem is + return nil, ErrRemoteServerFailed + } + + // Get the body data from the response + data, err := io.ReadAll(result.Body) + if err != nil { + return nil, err + } + + // Then try and parse it into webfinger data + webfinger := WebfingerData{} + err = json.Unmarshal(data, &webfinger) + if err != nil { + return nil, err + } + + return &webfinger, nil +} diff --git a/config/config.go b/config/config.go index 4f13033..1b85427 100644 --- a/config/config.go +++ b/config/config.go @@ -1,26 +1,161 @@ package config +import ( + "fmt" + "os" + + "github.com/BurntSushi/toml" + "github.com/rs/zerolog/log" + "gitlab.com/mstarongitlab/goutils/other" +) + type ConfigSSL struct { - HandleSSL bool // Whether Linstrom should handle SSL encryption itself + HandleSSL bool `toml:"handle_ssl"` // Whether Linstrom should handle SSL encryption itself // If Linstrom is to handle SSL, whether it should use LetsEncrypt for certificates - UseLetsEncrypt *bool + UseLetsEncrypt *bool `toml:"use_lets_encrypt"` // Path to the certificate if Linstrom is to handle SSL while not using LetsEncrypt - CertificateFile *string + CertificateFile *string `toml:"certificate_file"` // Mail adress to use in case of using LetsEncrypt - AdminMail *string + AdminMail *string `toml:"admin_mail"` } type ConfigGeneral struct { - Domain string // The domain this server operates under + Protocol string `toml:"protocol"` // The protocol with which to access the server publicly (http/https) + // The subdomain under which the server lives (example: "linstrom" if the full domain is linstrom.example.com) + Subdomain *string `toml:"subdomain"` + // The root domain under which the server lives (example: "example.com" if the full domain is linstrom.example.com) + Domain string `toml:"domain"` + // The port on which the server runs on + PrivatePort int `toml:"private_port"` + // The port under which the public can reach the server (useful if running behind a reverse proxy) + PublicPort *int `toml:"public_port"` + // Url to the database to use. Can be a postgres url or the path to a sqlite file + Database string `toml:"database"` + // Whether the given database url is for a postgres server. If not set, assumes sqlite + DbIsPostgres *bool `toml:"db_is_postgres"` +} + +type ConfigWebAuthn struct { + DisplayName string `toml:"display_name"` + HashingSecret string `toml:"hashing_secret"` } type ConfigAdmin struct { - Username string - PasswordHash string + // Name of the server's root admin account + Username string `toml:"username"` + // A one time password used to verify account access to the root admin + // after a server has been created and before the account could be linked to a passkey + FirstTimeSetupOTP string `toml:"first_time_setup_otp"` } type Config struct { - General ConfigGeneral - SSL ConfigSSL - Admin ConfigAdmin + General ConfigGeneral `toml:"general"` + SSL ConfigSSL `toml:"ssl"` + Admin ConfigAdmin `toml:"admin"` + Webauthn ConfigWebAuthn `toml:"webauthn"` +} + +var GlobalConfig Config + +var defaultConfig Config = Config{ + General: ConfigGeneral{ + Protocol: "http", + Subdomain: nil, + Domain: "localhost", + PrivatePort: 8080, + PublicPort: nil, + Database: "db.sqlite", + DbIsPostgres: other.IntoPointer(false), + }, + SSL: ConfigSSL{ + HandleSSL: false, + UseLetsEncrypt: nil, + CertificateFile: nil, + AdminMail: nil, + }, + Admin: ConfigAdmin{ + Username: "server-admin", + FirstTimeSetupOTP: "Example otp password", + }, + Webauthn: ConfigWebAuthn{ + DisplayName: "Linstrom", + HashingSecret: "some super secure secret that should never be changed or else password storage breaks", + }, +} + +func (gc *ConfigGeneral) GetFullDomain() string { + if gc.Subdomain != nil { + return *gc.Subdomain + gc.Domain + } + return gc.Domain +} + +func (gc *ConfigGeneral) GetFullPublicUrl() string { + str := gc.Protocol + gc.GetFullDomain() + if gc.PublicPort != nil { + str += fmt.Sprint(*gc.PublicPort) + } else { + str += fmt.Sprint(gc.PrivatePort) + } + return str +} + +func WriteDefaultConfig(toFile string) error { + log.Trace().Caller().Send() + log.Info().Str("config-file", toFile).Msg("Writing default config to file") + file, err := os.Create(toFile) + if err != nil { + log.Error(). + Err(err). + Str("config-file", toFile). + Msg("Failed to create file for default config") + return err + } + defer file.Close() + + data, err := toml.Marshal(&defaultConfig) + if err != nil { + log.Error().Err(err).Msg("Failed to marshal default config to toml") + return err + } + _, err = file.Write(data) + if err != nil { + log.Error().Err(err).Str("config-file", toFile).Msg("Failed to write default config") + return err + } + + log.Info().Str("config-file", toFile).Msg("Wrote default config") + return nil +} + +func ReadAndWriteToGlobal(fileName string) error { + log.Trace().Caller().Send() + log.Debug().Str("config-file", fileName).Msg("Attempting to read config file") + data, err := os.ReadFile(fileName) + if err != nil { + log.Warn(). + Str("config-file", fileName). + Err(err). + Msg("Failed to read config file, attempting to write default config") + err = WriteDefaultConfig(fileName) + if err != nil { + log.Error(). + Err(err). + Str("config-file", fileName). + Msg("Failed to create default config file") + return err + } + GlobalConfig = defaultConfig + return nil + } + config := Config{} + log.Debug().Str("config-file", fileName).Msg("Read config file, attempting to unmarshal") + err = toml.Unmarshal(data, &config) + if err != nil { + log.Error().Err(err).Bytes("config-data-raw", data).Msg("Failed to unmarshal config file") + return err + } + GlobalConfig = config + log.Info().Str("config-file", fileName).Msg("Read and applied config file") + return nil } diff --git a/flags.go b/flags.go new file mode 100644 index 0000000..3875190 --- /dev/null +++ b/flags.go @@ -0,0 +1,25 @@ +package main + +import "flag" + +var ( + flagPrettyPrint *bool = flag.Bool( + "pretty", + false, + "If set, pretty prints logging entries which would be json objects otherwise", + ) + flagConfigFile *string = flag.String( + "config", + "config.toml", + "Location of the config file. Defaults to \"config.toml\"", + ) + flagLogLevel *string = flag.String( + "loglevel", + "Info", + "Set the logging level. Options are: Trace, Debug, Info, Warning, Error, Fatal. Capitalisation doesn't matter. Defaults to Info", + ) +) + +func init() { + flag.Parse() +} diff --git a/frontend b/frontend new file mode 160000 index 0000000..94f7c14 --- /dev/null +++ b/frontend @@ -0,0 +1 @@ +Subproject commit 94f7c146f67651d44c190163c8ac6312024dea90 diff --git a/go.mod b/go.mod index a168303..e8ccb6c 100644 --- a/go.mod +++ b/go.mod @@ -1,37 +1,50 @@ module gitlab.com/mstarongitlab/linstrom -go 1.22.2 +go 1.23 + +toolchain go1.23.0 require ( - github.com/piprate/json-gold v0.5.0 - github.com/sirupsen/logrus v1.9.3 + github.com/BurntSushi/toml v1.4.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-webauthn/webauthn v0.11.2 + github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e + github.com/rs/zerolog v1.33.0 + gitlab.com/mstarongitlab/goutils v1.3.0 + gorm.io/driver/postgres v1.5.7 gorm.io/gorm v1.25.10 ) require ( + github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.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/go-webauthn/x v0.1.14 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/google/go-tpm v0.9.0 // indirect + github.com/google/go-tpm v0.9.1 // 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/kr/text v0.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // 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 + gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect diff --git a/go.sum b/go.sum index 7a158cb..71faad2 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,31 @@ +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 h1:cUoduvNB9/JFJYEVeKy2hX/R5qyg2uwnHVhXCIjhBeI= +github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38/go.mod h1:a0PnbhSGmzFMGx9KdEqfEdDHoEwTF9KLNbKLcWD8kAo= 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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.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/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= +github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= +github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0= +github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM= +github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 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= @@ -31,40 +38,55 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD 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/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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/go.mod h1:WZ501QQMbZZ+3pXFPhQKzNwS1+jls0oqov3uQ2WasLs= +github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e h1:BjuYFWZZd3O3RCMezWeO1CaJAOiSv2U3Pd8+nl9gDvA= +github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e/go.mod h1:vsjtQX07PZmKGSwixqXoKg6bvo3GTCA0GIwjCQ6qpHI= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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= +gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb h1:3hzIIECbUNKm2ukwq31KjFc3+s2oixSxjW68HFcRGQM= +gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb/go.mod h1:rt9IYvJBPh1z6t+vvzifmxDtGjGlr8683tSPfa5dbXI= +gitlab.com/mstarongitlab/goutils v1.3.0 h1:uuxPHjIU36lyJ8/z4T2xI32zOyh53Xj0Au8K12qkaJ4= +gitlab.com/mstarongitlab/goutils v1.3.0/go.mod h1:SvqfzFxgashuZPqR9kPwQ9gFA7I1yskZjhmGmY2pAow= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= 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= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..70a3298 --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "os" + "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "gitlab.com/mstarongitlab/linstrom/ap" + "gitlab.com/mstarongitlab/linstrom/config" +) + +func main() { + if *flagPrettyPrint { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + log.Info().Msg("Pretty logging enabled") + } + setLogLevel() + if err := config.ReadAndWriteToGlobal(*flagConfigFile); err != nil { + log.Fatal(). + Err(err). + Str("config-file", *flagConfigFile). + Msg("Failed to read config and couldn't write default") + } + // "@aufricus_athudath@activitypub.academy" + res, err := ap.GetAccountWebfinger("@aufricus_athudath@activitypub.academy") + log.Info(). + Err(err). + Any("webfinger", res). + Msg("Webfinger request result for @aufricus_athudath@activitypub.academy") +} + +func setLogLevel() { + log.Info().Str("new-level", *flagLogLevel).Msg("Attempting to set log level") + switch strings.ToLower(*flagLogLevel) { + case "trace": + zerolog.SetGlobalLevel(zerolog.TraceLevel) + case "debug": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "info": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "warn": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "error": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + case "fatal": + zerolog.SetGlobalLevel(zerolog.FatalLevel) + default: + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } +} diff --git a/server/endpoints_ap.go b/server/endpoints_ap.go index ce37bb1..e76176b 100644 --- a/server/endpoints_ap.go +++ b/server/endpoints_ap.go @@ -1,36 +1,38 @@ package server import ( + "errors" "fmt" "net/http" "strings" - "github.com/sirupsen/logrus" - "gitlab.com/mstarongitlab/linstrom/server/middlewares" + "github.com/rs/zerolog" "gitlab.com/mstarongitlab/linstrom/storage" + "gorm.io/gorm" ) // 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 - } + logger := zerolog.Ctx(r.Context()) 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") + logger.Debug().Msg("Resource parameter missing. Cancelling") return } accName := strings.TrimPrefix(requestedResource, "acc:") - acc, err := store.FindLocalAccount(accName) + acc, err := store.FindAccountByFullHandle(accName) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - logEntry.WithError(err).Warningln("couldn't find account") - return + if errors.Is(err, gorm.ErrRecordNotFound) { + http.Error(w, "account not found", http.StatusNotFound) + logger.Debug().Str("account-name", accName).Msg("Account not found") + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + logger.Error().Err(err).Msg("Error while searching for account") + return + } } fmt.Fprint(w, acc) } diff --git a/server/middlewares/injectLogrus.go b/server/middlewares/injectLogrus.go index af68322..5566769 100644 --- a/server/middlewares/injectLogrus.go +++ b/server/middlewares/injectLogrus.go @@ -1,10 +1,9 @@ package middlewares import ( - "context" "net/http" - "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) const CONTEXT_KEY_LOGRUS = ContextKey("logrus") @@ -12,9 +11,7 @@ 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) + newContext := log.With().Str("url-path", r.URL.RawPath).Logger().WithContext(r.Context()) next.ServeHTTP(w, r.WithContext(newContext)) }) } diff --git a/storage/errors.go b/storage/errors.go new file mode 100644 index 0000000..a9bc1e1 --- /dev/null +++ b/storage/errors.go @@ -0,0 +1,7 @@ +package storage + +type NotImplementedError struct{} + +func (n NotImplementedError) Error() string { + return "Not implemented yet" +} diff --git a/storage/mediaFile.go b/storage/mediaFile.go index 6edd1fa..7ae3cae 100644 --- a/storage/mediaFile.go +++ b/storage/mediaFile.go @@ -7,9 +7,9 @@ import ( ) type MediaFile struct { - ID string `gorm:"primarykey"` // The unique ID of this media file - CreatedAt time.Time // When this entry was created - UpdatedAt time.Time // When this entry was last updated + ID string `gorm:"primarykey"` // The unique ID of this media file + CreatedAt time.Time // When this entry was created + UpdatedAt time.Time // When this entry was last updated // When this entry was deleted (for soft deletions) // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to // If not null, this entry is marked as deleted @@ -25,12 +25,3 @@ type MediaFile struct { // Caching user and server icons locally however should reduce burden on remote servers by quite a bit though LocallyCached bool } - -// Placeholder media file. Acts as placeholder for media file fields that have not been initialised yet but need a value -var placeholderMediaFile = &MediaFile{ - ID: "placeholder", - Remote: false, - Link: "placeholder", // TODO: Replace this with a file path to a staticly included image - Type: "image/png", - LocallyCached: true, -} diff --git a/storage/notes.go b/storage/notes.go index 930ec7f..f5f14d9 100644 --- a/storage/notes.go +++ b/storage/notes.go @@ -7,9 +7,9 @@ import ( ) type Note struct { - ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations - CreatedAt time.Time // When this entry was created - UpdatedAt time.Time // When this entry was last updated + ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations + CreatedAt time.Time // When this entry was created + UpdatedAt time.Time // When this entry was last updated // When this entry was deleted (for soft deletions) // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to // If not null, this entry is marked as deleted @@ -29,19 +29,3 @@ type Note struct { OriginServer string // Url of the origin server. Also the primary key for those Tags []string `gorm:"serializer:json"` // Hashtags } - -var placeholderNote = &Note{ - ID: "placeholder", - Creator: "placeholder", - Remote: false, - RawContent: "placeholder", - ContentWarning: nil, - Attachments: []string{}, - Emotes: []string{}, - RepliesTo: nil, - Quotes: nil, - Target: NOTE_TARGET_HOME, - Pings: []string{}, - OriginServer: "placeholder", - Tags: []string{}, -} diff --git a/storage/passkeySessions.go b/storage/passkeySessions.go new file mode 100644 index 0000000..aaba918 --- /dev/null +++ b/storage/passkeySessions.go @@ -0,0 +1,45 @@ +package storage + +import ( + "github.com/go-webauthn/webauthn/webauthn" + "github.com/google/uuid" + "github.com/rs/zerolog/log" +) + +type PasskeySession struct { + ID string `gorm:"primarykey"` + Data webauthn.SessionData `gorm:"serializer:json"` +} + +// ---- Section SessionStore + +func (s *Storage) GenSessionID() (string, error) { + x := uuid.NewString() + log.Debug().Str("session-id", x).Msg("Generated new passkey session id") + return x, nil +} + +func (s *Storage) GetSession(sessionId string) (*webauthn.SessionData, bool) { + log.Debug().Str("id", sessionId).Msg("Looking for passkey session") + session := PasskeySession{} + res := s.db.Where("id = ?", sessionId).First(&session) + if res.Error != nil { + return nil, false + } + log.Debug().Str("id", sessionId).Any("webauthn-data", &session).Msg("Found passkey session") + return &session.Data, true +} + +func (s *Storage) SaveSession(token string, data *webauthn.SessionData) { + log.Debug().Str("id", token).Any("webauthn-data", data).Msg("Saving passkey session") + session := PasskeySession{ + ID: token, + Data: *data, + } + s.db.Save(&session) +} + +func (s *Storage) DeleteSession(token string) { + log.Debug().Str("id", token).Msg("Deleting passkey session (if one exists)") + s.db.Delete(&PasskeySession{ID: token}) +} diff --git a/storage/remoteServerInfo.go b/storage/remoteServerInfo.go index 395b97a..860cda7 100644 --- a/storage/remoteServerInfo.go +++ b/storage/remoteServerInfo.go @@ -7,9 +7,9 @@ import ( ) type RemoteServer struct { - ID string `gorm:"primarykey"` // ID is also server url - CreatedAt time.Time // When this entry was created - UpdatedAt time.Time // When this entry was last updated + ID string `gorm:"primarykey"` // ID is also server url + CreatedAt time.Time // When this entry was created + UpdatedAt time.Time // When this entry was last updated // When this entry was deleted (for soft deletions) // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to // If not null, this entry is marked as deleted @@ -19,11 +19,3 @@ type RemoteServer struct { Icon string // ID of a media file IsSelf bool // Whether this server is yours truly } - -var placeholderServer = &RemoteServer{ - ID: "placeholder", - ServerType: REMOTE_SERVER_LINSTROM, - Name: "placeholder", - Icon: "placeholder", - IsSelf: false, -} diff --git a/storage/remoteUser.go b/storage/remoteUser.go new file mode 100644 index 0000000..2157ae9 --- /dev/null +++ b/storage/remoteUser.go @@ -0,0 +1,5 @@ +package storage + +func (s *Storage) NewRemoteUser(fullHandle string) (*Account, error) { + return nil, nil +} diff --git a/storage/roles.go b/storage/roles.go new file mode 100644 index 0000000..55d8c31 --- /dev/null +++ b/storage/roles.go @@ -0,0 +1,8 @@ +package storage + +type Role struct { + // Name of the role + Name string + // If set, counts as all permissions being set and all restrictions being disabled + FullAdmin bool +} diff --git a/storage/storage.go b/storage/storage.go index 12c92fd..1d2d8c8 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,8 +1,6 @@ package storage import ( - "fmt" - "github.com/glebarez/sqlite" "gorm.io/driver/postgres" "gorm.io/gorm" @@ -35,34 +33,16 @@ func storageFromEmptyDb(db *gorm.DB) (*Storage, error) { // AutoMigrate ensures the db is in a state where all the structs given here // have their own tables and relations setup. It also updates tables if necessary db.AutoMigrate( - placeholderMediaFile, - placeholderUser(), - placeholderNote, - placeholderServer, + MediaFile{}, + Account{}, + RemoteServer{}, + Note{}, + Role{}, + PasskeySession{}, ) - // Afterwards add the placeholder entries for each table. - // FirstOrCreate either creates a new entry or retrieves the first matching one - // We only care about the creation if there is none yet, so no need to carry the result over - if res := db.FirstOrCreate(placeholderMediaFile); res.Error != nil { - return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) - } - if res := db.FirstOrCreate(placeholderUser()); res.Error != nil { - return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) - } - if res := db.FirstOrCreate(placeholderNote); res.Error != nil { - return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) - } - if res := db.FirstOrCreate(placeholderServer); res.Error != nil { - return nil, fmt.Errorf("failed to add placeholder media file: %w", res.Error) - } // And finally, build the actual storage struct 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 -} diff --git a/storage/user.go b/storage/user.go index fed4001..47a3590 100644 --- a/storage/user.go +++ b/storage/user.go @@ -1,21 +1,30 @@ package storage import ( + "crypto/rand" + "errors" "time" "github.com/go-webauthn/webauthn/webauthn" - "github.com/google/uuid" + "github.com/mstarongithub/passkey" + "github.com/rs/zerolog/log" + "gitlab.com/mstarongitlab/linstrom/ap" + "gitlab.com/mstarongitlab/linstrom/config" "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 a uuid for this account - Handle string // Handle 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 +type Account struct { + ID string `gorm:"primarykey"` // ID is a uuid for this account + // Handle of the user (eg "max" if the full username is @max@example.com) + // Assume unchangable (once set by a user) to be kind to other implementations + // Would be an easy avenue to fuck with them though + Handle string + CreatedAt time.Time // When this entry was created. Automatically set by gorm + // When this account was last updated. Will also be used for refreshing remote accounts. Automatically set by gorm + UpdatedAt time.Time // When this entry was deleted (for soft deletions) // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to // If not null, this entry is marked as deleted @@ -39,57 +48,230 @@ type User struct { 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 + // a cyborg-catgirl/boy/human or a cathuman shaped robot, refer to Gender for pronouns + IdentifiesAs []Being `gorm:"serializer:json"` // 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 + Gender []string `gorm:"serializer:json"` + // The roles assocciated with an account + Roles []string `gorm:"serializer:json"` // --- 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 + PrivateKeyPem *string // The private key of the account. Nil if remote user + WebAuthnId []byte // The unique and random ID of this account used for passkey authentication + // Whether the account got verified and is allowed to be active + // For local accounts being active means being allowed to login and perform interactions + // For remote users, if an account is not verified, any interactions it sends are discarded + Verified bool + PasskeyCredentials []webauthn.Credential `gorm:"serializer:json"` // Webauthn credentials data + // Has a RemoteAccountLinks included if remote user + RemoteLinks *RemoteAccountLinks } -func NewEmptyUser() *User { - return &User{ - ID: uuid.NewString(), - Handle: "placeholder", - Remote: false, - Server: "placeholder", - DisplayName: "placeholder", - CustomFields: []uint{}, - Description: "placeholder", - Tags: []string{}, - IsBot: true, - Follows: []string{}, - Followers: []string{}, - Icon: "placeholder", - Background: "placeholder", - Banner: "placeholder", - Indexable: false, - PublicKeyPem: nil, - RestrictedFollow: false, - IdentifiesAs: []Being{BEING_ROBOT}, - Gender: []string{"it", "its"}, - PasswordHash: []byte("placeholder"), - TotpToken: []byte("placeholder"), - Passkeys: map[string]webauthn.Credential{}, - PrivateKeyPem: nil, +// Contains static and cached info about a remote account, mostly links +type RemoteAccountLinks struct { + // ---- Section: gorm + // Sets this struct up as a value that an Account may have + gorm.Model + AccountID string + + // Just about every link here is optional to accomodate for servers with only minimal accounts + // Minimal being handle, ap link and inbox + ApLink string + ViewLink *string + FollowersLink *string + FollowingLink *string + InboxLink string + OutboxLink *string + FeaturedLink *string + FeaturedTagsLink *string +} + +// Find an account in the db using a given full handle (@max@example.com) +// Returns an account and nil if an account is found, otherwise nil and the error +func (s *Storage) FindAccountByFullHandle(handle string) (*Account, error) { + log.Trace().Caller().Send() + log.Debug().Str("account-handle", handle).Msg("Looking for account by handle") + name, server, err := ap.SplitFullHandle(handle) + if err != nil { + log.Warn().Err(err).Str("account-handle", handle).Msg("Failed to split up account handle") + return nil, err } + + acc := Account{} + res := s.db.Where("name = ?", name).Where("server = ?", server).First(&acc) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + log.Info().Str("account-handle", handle).Msg("Account with handle not found") + } else { + log.Error().Err(err).Str("account-handle", handle).Msg("Failed to get account with handle") + } + return nil, res.Error + } + log.Info().Str("account-handle", handle).Msg("Found account") + return &acc, nil } -func placeholderUser() *User { - tmp := NewEmptyUser() - tmp.ID = "placeholder" - return tmp +// Find an account given a specific ID +func (s *Storage) FindAccountById(id string) (*Account, error) { + log.Trace().Caller().Send() + log.Debug().Str("account-id", id).Msg("Looking for account by id") + acc := Account{} + res := s.db.First(&acc, id) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + log.Warn().Str("account-id", id).Msg("Account not found") + } else { + log.Error().Err(res.Error).Str("account-id", id).Msg("Failed to look for account") + } + return nil, res.Error + } + log.Info().Str("account-id", id).Msg("Found account") + return &acc, nil +} + +func (s *Storage) NewEmptyAccount() (*Account, error) { + log.Trace().Caller().Send() + log.Debug().Msg("Creating new empty account") + acc := Account{} + // Generate the 64 bit id for passkey and webauthn stuff + data := make([]byte, 64) + c, err := rand.Read(data) + for err != nil || c != len(data) || c < 64 { + data = make([]byte, 64) + c, err = rand.Read(data) + } + acc.WebAuthnId = data + acc.Followers = []string{} + acc.Tags = []string{} + acc.Follows = []string{} + acc.Gender = []string{} + acc.CustomFields = []uint{} + acc.IdentifiesAs = []Being{} + acc.PasskeyCredentials = []webauthn.Credential{} + res := s.db.Save(acc) + if res.Error != nil { + log.Error().Err(res.Error).Msg("Failed to safe new account") + return nil, res.Error + } + log.Info().Str("account-id", acc.ID).Msg("Created new account") + return &acc, nil +} + +func (s *Storage) NewLocalAccount(handle string) (*Account, error) { + log.Trace().Caller().Send() + log.Debug().Str("account-handle", handle).Msg("Creating new local account") + acc, err := s.NewEmptyAccount() + if err != nil { + log.Error().Err(err).Msg("Failed to create empty account for use") + return nil, err + } + acc.Handle = handle + acc.Server = config.GlobalConfig.General.GetFullDomain() + acc.Remote = false + acc.DisplayName = handle + + log.Debug(). + Str("account-handle", handle). + Str("account-id", acc.ID). + Msg("Saving new local account") + res := s.db.Save(acc) + if res.Error != nil { + log.Error().Err(res.Error).Any("account-full", acc).Msg("Failed to save local account") + return nil, res.Error + } + log.Info(). + Str("account-handle", handle). + Str("account-id", acc.ID). + Msg("Created new local account") + return acc, nil +} + +// ---- Section WebAuthn.User +// Implements the webauthn.User interface for interaction with passkeys + +func (a *Account) WebAuthnID() []byte { + log.Trace().Caller().Send() + return a.WebAuthnId +} + +func (u *Account) WebAuthnName() string { + log.Trace().Caller().Send() + return u.Handle +} + +func (u *Account) WebAuthnDisplayName() string { + log.Trace().Caller().Send() + return u.DisplayName +} + +func (u *Account) WebAuthnCredentials() []webauthn.Credential { + log.Trace().Caller().Send() + return u.PasskeyCredentials +} + +func (u *Account) WebAuthnIcon() string { + log.Trace().Caller().Send() + return "" +} + +// ---- Section passkey.User +// Implements the passkey.User interface + +func (u *Account) PutCredential(new webauthn.Credential) { + log.Trace().Caller().Send() + u.PasskeyCredentials = append(u.PasskeyCredentials, new) +} + +// Section passkey.UserStore +// Implements the passkey.UserStore interface + +func (s *Storage) GetOrCreateUser(userID string) passkey.User { + log.Trace().Caller().Send() + log.Debug(). + Str("account-handle", userID). + Msg("Looking for or creating account for passkey stuff") + acc := &Account{} + res := s.db.Where(Account{Handle: userID, Server: config.GlobalConfig.General.GetFullDomain()}). + First(acc) + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + log.Debug().Str("account-handle", userID) + var err error + acc, err = s.NewLocalAccount(userID) + if err != nil { + log.Error(). + Err(err). + Str("account-handle", userID). + Msg("Failed to create new account for webauthn request") + return nil + } + } + return acc +} + +func (s *Storage) GetUserByWebAuthnId(id []byte) passkey.User { + log.Trace().Caller().Send() + log.Debug().Bytes("webauthn-id", id).Msg("Looking for account with webauthn id") + acc := Account{} + res := s.db.Where(Account{WebAuthnId: id}).First(&acc) + if res.Error != nil { + log.Error(). + Err(res.Error). + Bytes("webauthn-id", id). + Msg("Failed to find user with webauthn ID") + return nil + } + log.Info().Msg("Found account with given webauthn id") + return &acc +} + +func (s *Storage) SaveUser(rawUser passkey.User) { + log.Trace().Caller().Send() + user, ok := rawUser.(*Account) + if !ok { + log.Error().Any("raw-user", rawUser).Msg("Failed to cast raw user to db account") + } + s.db.Save(user) } diff --git a/storage/userIdentType.go b/storage/userIdentType.go index 7ce7df5..3693853 100644 --- a/storage/userIdentType.go +++ b/storage/userIdentType.go @@ -1,10 +1,5 @@ package storage -import ( - "database/sql/driver" - "errors" -) - // What kind of being a user identifies as type Being string @@ -16,16 +11,3 @@ const ( 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") - } -} From 870923885908261234f449cb1c23e8b45b0ae2d5 Mon Sep 17 00:00:00 2001 From: mStar Date: Fri, 6 Sep 2024 23:01:57 +0200 Subject: [PATCH 03/13] Added goals, cache and func to get any remote obj Cache is for storage, also includes pooled encoders and decoders goals are things to eventually add to Linstrom --- ap/getRemoteUser.go | 60 +++- config/config.go | 39 ++- go.mod | 31 +- go.sum | 513 +++++++++++++++++++++++++++++++++- goals.md | 20 ++ storage/cache/cache.go | 63 +++++ storage/cache/coderPools.go | 162 +++++++++++ storage/cache/lockedCoders.go | 35 +++ 8 files changed, 907 insertions(+), 16 deletions(-) create mode 100644 goals.md create mode 100644 storage/cache/cache.go create mode 100644 storage/cache/coderPools.go create mode 100644 storage/cache/lockedCoders.go diff --git a/ap/getRemoteUser.go b/ap/getRemoteUser.go index 6876088..aabe689 100644 --- a/ap/getRemoteUser.go +++ b/ap/getRemoteUser.go @@ -1,11 +1,17 @@ package ap import ( + "errors" + "fmt" + "io" "net/http" + "time" "gitlab.com/mstarongitlab/goap" ) +var ErrNoApUrl = errors.New("no Activitypub url in webfinger") + func GetRemoteUser(fullHandle string) (goap.BaseApChain, error) { webfinger, err := GetAccountWebfinger(fullHandle) if err != nil { @@ -18,10 +24,62 @@ func GetRemoteUser(fullHandle string) (goap.BaseApChain, error) { apUrl = *link.Href } } + if apUrl == "" { + return nil, ErrNoApUrl + } apRequest, err := http.NewRequest("GET", apUrl, nil) if err != nil { return nil, err } apRequest.Header.Add("Accept", "application/activity+json,application/ld+json,application/json") - return nil, nil + client := http.Client{Timeout: time.Second * 30} + res, err := client.Do(apRequest) + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("bad status code: %d", res.StatusCode) + } + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + apObject, _ := goap.Unmarshal(body, nil, nil) + // Check if Id exists + if _, ok := goap.FindAttribute[*goap.UDIdData](apObject); !ok { + return nil, fmt.Errorf("missing attribute for account: Id") + } + // Check that it has the correct object type for an account + if objTypePtr, ok := goap.FindAttribute[*goap.UDTypeData](apObject); !ok { + return nil, fmt.Errorf("missing attribute for account: Type") + } else if objType := *objTypePtr; objType.Type != goap.KEY_ACTIVITYSTREAMS_ACTOR { + return nil, fmt.Errorf("wrong ap object type: %s", objType.Type) + } + // And finally check for inbox + if _, ok := goap.FindAttribute[*goap.W3InboxData](apObject); !ok { + return nil, fmt.Errorf("missing attribute for account: Inbox") + } + return apObject, nil +} + +func GetRemoteObject(target string) (goap.BaseApChain, error) { + apRequest, err := http.NewRequest("GET", target, nil) + if err != nil { + return nil, err + } + apRequest.Header.Add("Accept", "application/activity+json,application/ld+json,application/json") + client := http.Client{Timeout: time.Second * 30} + res, err := client.Do(apRequest) + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("bad status code: %d", res.StatusCode) + } + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + apObject, _ := goap.Unmarshal(body, nil, nil) + return apObject, nil } diff --git a/config/config.go b/config/config.go index 1b85427..428eff1 100644 --- a/config/config.go +++ b/config/config.go @@ -29,10 +29,6 @@ type ConfigGeneral struct { PrivatePort int `toml:"private_port"` // The port under which the public can reach the server (useful if running behind a reverse proxy) PublicPort *int `toml:"public_port"` - // Url to the database to use. Can be a postgres url or the path to a sqlite file - Database string `toml:"database"` - // Whether the given database url is for a postgres server. If not set, assumes sqlite - DbIsPostgres *bool `toml:"db_is_postgres"` } type ConfigWebAuthn struct { @@ -48,24 +44,38 @@ type ConfigAdmin struct { FirstTimeSetupOTP string `toml:"first_time_setup_otp"` } +type ConfigStorage struct { + // Url to the database to use + // If DbIsPostgres is either not set or false, the url is expected to be a path to a sqlite file + // Otherwise, it's expected to be an url to a postgres server + DatabaseUrl string `toml:"database_url"` + // Whether the target of the database url is a postgres server + DbIsPostgres *bool `toml:"db_is_postgres,omitempty"` + // Whether to use Redis for caching in addition to an in memory one + UseRedis bool `toml:"use_redis"` + // Url to redis server. Expected to be set if UseRedis is true + RedisUrl *string `toml:"redis_url,omitempty"` + // The maximum size of the in-memory cache in bytes + MaxInMemoryCacheSize int64 `toml:"max_in_memory_cache_size"` +} + type Config struct { General ConfigGeneral `toml:"general"` SSL ConfigSSL `toml:"ssl"` Admin ConfigAdmin `toml:"admin"` Webauthn ConfigWebAuthn `toml:"webauthn"` + Storage ConfigStorage `toml:"storage"` } var GlobalConfig Config var defaultConfig Config = Config{ General: ConfigGeneral{ - Protocol: "http", - Subdomain: nil, - Domain: "localhost", - PrivatePort: 8080, - PublicPort: nil, - Database: "db.sqlite", - DbIsPostgres: other.IntoPointer(false), + Protocol: "http", + Subdomain: nil, + Domain: "localhost", + PrivatePort: 8080, + PublicPort: nil, }, SSL: ConfigSSL{ HandleSSL: false, @@ -81,6 +91,13 @@ var defaultConfig Config = Config{ DisplayName: "Linstrom", HashingSecret: "some super secure secret that should never be changed or else password storage breaks", }, + Storage: ConfigStorage{ + DatabaseUrl: "db.sqlite", + DbIsPostgres: other.IntoPointer(false), + UseRedis: false, + RedisUrl: nil, + MaxInMemoryCacheSize: 1e6, // 1 Megabyte + }, } func (gc *ConfigGeneral) GetFullDomain() string { diff --git a/go.mod b/go.mod index e8ccb6c..887ecde 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,9 @@ toolchain go1.23.0 require ( github.com/BurntSushi/toml v1.4.0 + github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 + github.com/eko/gocache/lib/v4 v4.1.6 + github.com/eko/gocache/store/memcache/v4 v4.2.2 github.com/glebarez/sqlite v1.11.0 github.com/go-webauthn/webauthn v0.11.2 github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e @@ -15,6 +18,30 @@ require ( gorm.io/gorm v1.25.10 ) +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/eko/gocache/store/redis/v4 v4.2.2 // indirect + github.com/eko/gocache/store/ristretto/v4 v4.2.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/piprate/json-gold v0.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/redis/go-redis/v9 v9.0.2 // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/sync v0.8.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect +) + require ( github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -24,7 +51,7 @@ require ( github.com/go-webauthn/x v0.1.14 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/go-tpm v0.9.1 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/google/uuid v1.6.0 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 @@ -40,7 +67,7 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/x448/float16 v0.8.4 // indirect - gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb // indirect + gitlab.com/mstarongitlab/goap v1.1.0 golang.org/x/crypto v0.26.0 // indirect golang.org/x/sys v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/go.sum b/go.sum index 71faad2..ef84456 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,62 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 h1:wAIE/kN63Oig1DdOzN7O+k4AbFh2cCJoKMFXrwRJtzk= +github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38 h1:cUoduvNB9/JFJYEVeKy2hX/R5qyg2uwnHVhXCIjhBeI= @@ -7,27 +64,118 @@ github.com/datainq/xml-date-time v0.0.0-20170820214645-2292f08baa38/go.mod h1:a0 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/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 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/eko/gocache/lib/v4 v4.1.6 h1:5WWIGISKhE7mfkyF+SJyWwqa4Dp2mkdX8QsZpnENqJI= +github.com/eko/gocache/lib/v4 v4.1.6/go.mod h1:HFxC8IiG2WeRotg09xEnPD72sCheJiTSr4Li5Ameg7g= +github.com/eko/gocache/store/memcache/v4 v4.2.2 h1:VKfxytQ5bkcfF3LhmgkrqRiEU2yCN2/rJBUvF1fKZJw= +github.com/eko/gocache/store/memcache/v4 v4.2.2/go.mod h1:9lFU3tZPiej8E3J4ueZ0K9kIdiDQpRxu6WhtId5OsZA= +github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0= +github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w= +github.com/eko/gocache/store/ristretto/v4 v4.2.2 h1:lXFzoZ5ck6Gy6ON7f5DHSkNt122qN7KoroCVgVwF7oo= +github.com/eko/gocache/store/ristretto/v4 v4.2.2/go.mod h1:uIvBVJzqRepr5L0RsbkfQ2iYfbyos2fuji/s4yM+aUM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0= github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM= github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 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= @@ -38,8 +186,24 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD 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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -47,46 +211,381 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e h1:BjuYFWZZd3O3RCMezWeO1CaJAOiSv2U3Pd8+nl9gDvA= github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e/go.mod h1:vsjtQX07PZmKGSwixqXoKg6bvo3GTCA0GIwjCQ6qpHI= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +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/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE= +github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb h1:3hzIIECbUNKm2ukwq31KjFc3+s2oixSxjW68HFcRGQM= -gitlab.com/mstarongitlab/goap v0.0.0-20240826151945-33a82e544bfb/go.mod h1:rt9IYvJBPh1z6t+vvzifmxDtGjGlr8683tSPfa5dbXI= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +gitlab.com/mstarongitlab/goap v1.1.0 h1:uN05RP+Tq2NR2IuPq6XQa5oLpfailpoEvxo1SfehxBA= +gitlab.com/mstarongitlab/goap v1.1.0/go.mod h1:rt9IYvJBPh1z6t+vvzifmxDtGjGlr8683tSPfa5dbXI= gitlab.com/mstarongitlab/goutils v1.3.0 h1:uuxPHjIU36lyJ8/z4T2xI32zOyh53Xj0Au8K12qkaJ4= gitlab.com/mstarongitlab/goutils v1.3.0/go.mod h1:SvqfzFxgashuZPqR9kPwQ9gFA7I1yskZjhmGmY2pAow= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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= @@ -94,6 +593,13 @@ 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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 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= @@ -102,3 +608,6 @@ 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= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/goals.md b/goals.md new file mode 100644 index 0000000..d79976d --- /dev/null +++ b/goals.md @@ -0,0 +1,20 @@ +## Easy +- optional content filter with Microsoft's ai scan thing (user and server level) +- lockdown mode (all incoming stuff will be bonked immediately) (user and server) +- Post highlighting (opposite of muting) where if a post contains some specific thing, it gets some highlight + - Maybe even with different highlighting options + +## Medium +- optional automatic server screening +- metadata sharing (thing like link previews or blocklists) +- asks (in some way that is compatible with wafrn hopefully) +- rss feed imports + +## Hard +- custom "ads" created and controlled by server admins +- some sort of subscription/payment system (opt-in (you have to opt in to potentially see monetised stuff in the first place)) +- extended account moderation (user and server) +- custom api for working around AP being a pos: + - includes messages always being encrypted + - bunch of other optimisations + diff --git a/storage/cache/cache.go b/storage/cache/cache.go new file mode 100644 index 0000000..3886ca3 --- /dev/null +++ b/storage/cache/cache.go @@ -0,0 +1,63 @@ +package cache + +import ( + "time" + + "github.com/dgraph-io/ristretto" + "github.com/eko/gocache/lib/v4/cache" + "github.com/eko/gocache/lib/v4/store" + redis_store "github.com/eko/gocache/store/redis/v4" + ristretto_store "github.com/eko/gocache/store/ristretto/v4" + "github.com/redis/go-redis/v9" + "github.com/rs/zerolog/log" + "gitlab.com/mstarongitlab/linstrom/config" +) + +type Cache struct { + cache *cache.ChainCache[[]byte] + decoders *DecoderPool + encoders *EncoderPool +} + +func NewCache() (*Cache, error) { + // ristretto is an in-memory cache + ristrettoCache, err := ristretto.NewCache(&ristretto.Config{ + // The *10 is a recommendation from ristretto + NumCounters: config.GlobalConfig.Storage.MaxInMemoryCacheSize * 10, + MaxCost: config.GlobalConfig.Storage.MaxInMemoryCacheSize, + BufferItems: 64, // Same here + }) + if err != nil { + return nil, err + } + ristrettoStore := ristretto_store.NewRistretto( + ristrettoCache, + store.WithExpiration(time.Second*10), + ) + + var cacheManager *cache.ChainCache[[]byte] + if config.GlobalConfig.Storage.UseRedis { + if config.GlobalConfig.Storage.RedisUrl == nil { + log.Fatal(). + Msg("Told to use redis in addition to in-memory store, but no redis url provided!") + } + opts, err := redis.ParseURL(*config.GlobalConfig.Storage.RedisUrl) + if err != nil { + return nil, err + } + redisClient := redis.NewClient(opts) + redisStore := redis_store.NewRedis(redisClient, store.WithExpiration(time.Minute)) + cacheManager = cache.NewChain( + cache.New[[]byte](ristrettoStore), + cache.New[[]byte](redisStore), + ) + } else { + cacheManager = cache.NewChain(cache.New[[]byte](ristrettoStore)) + } + + return &Cache{ + cache: cacheManager, + decoders: NewDecoderPool(), + encoders: NewEncoderPool(), + }, nil +} diff --git a/storage/cache/coderPools.go b/storage/cache/coderPools.go new file mode 100644 index 0000000..7172eb6 --- /dev/null +++ b/storage/cache/coderPools.go @@ -0,0 +1,162 @@ +package cache + +import ( + "io" + "sync" +) + +type EncoderPool struct { + encoders []*gobEncoder + lock sync.RWMutex +} + +func NewEncoderPool() *EncoderPool { + return &EncoderPool{ + encoders: []*gobEncoder{}, + lock: sync.RWMutex{}, + } +} + +// Encode some value with gob +func (p *EncoderPool) Encode(raw any) ([]byte, error) { + var encoder *gobEncoder + // First try to find an available encoder + // Read only lock should be fine here since locks are atomic i + //and thus no two goroutines should be able to lock the same encoder at the same time + // One of those attempts is going to fail and continue looking for another available one + p.lock.RLock() + for _, v := range p.encoders { + // If we can lock one, it's available + if v.TryLock() { + // Keep the reference, then break + encoder = v + break + } + } + p.lock.RUnlock() + // Didn't find an available encoder, create new one and add to pool + if encoder == nil { + encoder = p.expand() + } + // Ensure we free the encoder at the end + defer encoder.Unlock() + // Clear the buffer to avoid funky output from previous operations + encoder.Buffer.Reset() + encoder.Encoder.Encode(raw) + data, err := io.ReadAll(encoder.Buffer) + if err != nil { + return nil, err + } + return data, nil +} + +// Expands the pool of available encoders by one and returns a reference to the new one +// The new encoder is already locked and ready for use +func (p *EncoderPool) expand() *gobEncoder { + enc := newEncoder() + // Lock everything. First the pool fully since we need to overwrite the encoders slice + p.lock.Lock() + // And then the new encoder to make it available for use by the caller + // so that they don't have to search for it again + enc.Lock() + p.encoders = append(p.encoders, &enc) + p.lock.Unlock() + return &enc +} + +// Prune all encoders not currently used from the pool +func (p *EncoderPool) Prune() { + stillActiveEncoders := []*gobEncoder{} + p.lock.Lock() + for _, v := range p.encoders { + if !v.TryLock() { + // Can't lock, encoder in use, keep it + stillActiveEncoders = append(stillActiveEncoders, v) + continue + } + // If we reach here, the encoder was available (since not locked), unlock and continue + v.Unlock() + } + // Overwrite list of available encoders to only contain the ones we found to still be active + p.encoders = stillActiveEncoders + p.lock.Unlock() +} + +type DecoderPool struct { + encoders []*gobDecoder + lock sync.RWMutex +} + +func NewDecoderPool() *DecoderPool { + return &DecoderPool{ + encoders: []*gobDecoder{}, + lock: sync.RWMutex{}, + } +} + +// Decode some value with gob +func (p *DecoderPool) Encode(raw []byte, target any) error { + var encoder *gobDecoder + // First try to find an available encoder + // Read only lock should be fine here since locks are atomic i + //and thus no two goroutines should be able to lock the same encoder at the same time + // One of those attempts is going to fail and continue looking for another available one + p.lock.RLock() + for _, v := range p.encoders { + // If we can lock one, it's available + if v.TryLock() { + // Keep the reference, then break + encoder = v + break + } + } + p.lock.RUnlock() + // Didn't find an available encoder, create new one and add to pool + if encoder == nil { + encoder = p.expand() + } + // Desure we free the encoder at the end + defer encoder.Unlock() + // Clear the buffer to avoid funky output from previous operations + encoder.Buffer.Reset() + // Write the raw data to the buffer, then decode it + // The write will always succeed (or panic) + _, _ = encoder.Buffer.Write(raw) + err := encoder.Decoder.Decode(target) + if err != nil { + return err + } + return nil +} + +// Expands the pool of available encoders by one and returns a reference to the new one +// The new encoder is already locked and ready for use +func (p *DecoderPool) expand() *gobDecoder { + enc := newDecoder() + // Lock everything. First the pool fully since we need to overwrite the encoders slice + p.lock.Lock() + // And then the new encoder to make it available for use by the caller + // so that they don't have to search for it again + enc.Lock() + p.encoders = append(p.encoders, &enc) + p.lock.Unlock() + return &enc +} + +// Prune all encoders not currently used from the pool +func (p *DecoderPool) Prune() { + stillActiveDecoders := []*gobDecoder{} + p.lock.Lock() + for _, v := range p.encoders { + if !v.TryLock() { + // Can't lock, encoder in use, keep it + stillActiveDecoders = append(stillActiveDecoders, v) + continue + } + // If we reach here, the encoder was available (since not locked), unlock and continue + v.Unlock() + } + // Overwrite list of available encoders to only contain the ones we found to still be active + p.encoders = stillActiveDecoders + p.lock.Unlock() +} diff --git a/storage/cache/lockedCoders.go b/storage/cache/lockedCoders.go new file mode 100644 index 0000000..74f3ed7 --- /dev/null +++ b/storage/cache/lockedCoders.go @@ -0,0 +1,35 @@ +package cache + +import ( + "bytes" + "encoding/gob" + "sync" +) + +type gobEncoder struct { + sync.Mutex + Encoder *gob.Encoder + Buffer *bytes.Buffer +} + +func newEncoder() gobEncoder { + buf := bytes.Buffer{} + return gobEncoder{ + Encoder: gob.NewEncoder(&buf), + Buffer: &buf, + } +} + +type gobDecoder struct { + sync.Mutex + Decoder *gob.Decoder + Buffer *bytes.Buffer +} + +func newDecoder() gobDecoder { + buf := bytes.Buffer{} + return gobDecoder{ + Decoder: gob.NewDecoder(&buf), + Buffer: &buf, + } +} From a472cbcd15824efa268c3b616664e806904a48fe Mon Sep 17 00:00:00 2001 From: mStar Date: Mon, 9 Sep 2024 07:50:09 +0200 Subject: [PATCH 04/13] Update goals and add mod-tools "checklist" Added multiple frontends to goals mod-tools is a list of various mod tools without which Linstrom can not provide a full release. If any is missing, that is considered a broken state where no stable version may be released --- goals.md | 5 +++++ mod-tools.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 mod-tools.md diff --git a/goals.md b/goals.md index d79976d..5ada2da 100644 --- a/goals.md +++ b/goals.md @@ -18,3 +18,8 @@ - includes messages always being encrypted - bunch of other optimisations +# Variable difficutly +- Multiple built-in frontends + - Primary using ember, focus on good looking and most feature complete + - Modifyable using htmx (not sure on this one yet) + - Low resource/no script with pure html and Go templating diff --git a/mod-tools.md b/mod-tools.md new file mode 100644 index 0000000..dfa7331 --- /dev/null +++ b/mod-tools.md @@ -0,0 +1,50 @@ +- Note: All server actions could also be dependent on the type of server (Plemora, Misskey, Masto, GTS, Linstrom, etc) +- Bulk moderation: + - Maybe vim-like multiselect via search with regex? + - Role based selection? +- MRF: + - Automated actions on incoming and outgoing messages: + - Apply some regex + - Auto-mark as sensitive + - Mark media as sensitive + - Remove media + - Block entirely + - Force raw text + - Selection mechanism: + - Match a regex + - From user + - From server + - From user with role + - (Optional) On positive result from M$ content scan +- Automod: + - Notes (and replies) get withheld until approval + - Regex based yoink + - All from user + - All from role + - All from server + - (Optional) On positive result from M$ content scan + - Exclude roles from being hit + - Exclude users from being hit +- Local Role permissions: + - Can send activities (Everything) + - Can upload media + - Can send reactions + - Can send replies + - Can send posts (that are not replies) + - Can edit own posts + - Ratelimit (variable speed) +- Emoji management: + - Disable custom remote emoji per default + - Disable select ones + - Based on regex + - Based on specific names + - Based on origin +- Account management: + - Force reset password of local account + - Disable login into account + - Delete account +- Deletion policies: + - Note: Always going to be soft delete first, then hard delete x days later + - delete notifications get send on hard delete + - Delete notifications get send on soft delete + - Delete notifications get send y days after soft delete (where y < x) From 490b788e7bed303b2672a8904a41ca9ac7233bb6 Mon Sep 17 00:00:00 2001 From: mStar Date: Tue, 10 Sep 2024 07:24:30 +0200 Subject: [PATCH 05/13] add another goal Converter for database schemes into a format Linstrom uses --- goals.md | 1 + 1 file changed, 1 insertion(+) diff --git a/goals.md b/goals.md index 5ada2da..9d34010 100644 --- a/goals.md +++ b/goals.md @@ -9,6 +9,7 @@ - metadata sharing (thing like link previews or blocklists) - asks (in some way that is compatible with wafrn hopefully) - rss feed imports +- Database converter (Masto/Akoma/Mk -> Linstrom, maybe also other way around) ## Hard - custom "ads" created and controlled by server admins From 814316ab1e4ce86afac1a3a490cab17da01b1c33 Mon Sep 17 00:00:00 2001 From: mStar Date: Thu, 12 Sep 2024 08:56:57 +0200 Subject: [PATCH 06/13] Progress meow --- config/config.go | 8 ++-- frontend-noscript/index.html.gotmpl | 3 ++ main.go | 44 +++++++++++++++--- remoteStorage/remoteServer.go | 13 ++++++ storage/cache.go | 47 ++++++++++++++++++++ storage/cache/cache.go | 49 +++++++++++++++----- storage/cache/coderPools.go | 2 +- storage/notes.go | 24 +++++++++- storage/remoteServerInfo.go | 29 ++++++++++++ storage/storage.go | 26 +++++++---- storage/user.go | 69 ++++++++++++++++++++++++++--- 11 files changed, 279 insertions(+), 35 deletions(-) create mode 100644 frontend-noscript/index.html.gotmpl create mode 100644 remoteStorage/remoteServer.go create mode 100644 storage/cache.go diff --git a/config/config.go b/config/config.go index 428eff1..19f633b 100644 --- a/config/config.go +++ b/config/config.go @@ -29,6 +29,9 @@ type ConfigGeneral struct { PrivatePort int `toml:"private_port"` // The port under which the public can reach the server (useful if running behind a reverse proxy) PublicPort *int `toml:"public_port"` + // File to write structured logs to (structured being formatted as json) + // If not set, Linstrom won't write structured logs + StructuredLogFile *string } type ConfigWebAuthn struct { @@ -51,9 +54,7 @@ type ConfigStorage struct { DatabaseUrl string `toml:"database_url"` // Whether the target of the database url is a postgres server DbIsPostgres *bool `toml:"db_is_postgres,omitempty"` - // Whether to use Redis for caching in addition to an in memory one - UseRedis bool `toml:"use_redis"` - // Url to redis server. Expected to be set if UseRedis is true + // Url to redis server. If empty, no redis is used RedisUrl *string `toml:"redis_url,omitempty"` // The maximum size of the in-memory cache in bytes MaxInMemoryCacheSize int64 `toml:"max_in_memory_cache_size"` @@ -94,7 +95,6 @@ var defaultConfig Config = Config{ Storage: ConfigStorage{ DatabaseUrl: "db.sqlite", DbIsPostgres: other.IntoPointer(false), - UseRedis: false, RedisUrl: nil, MaxInMemoryCacheSize: 1e6, // 1 Megabyte }, diff --git a/frontend-noscript/index.html.gotmpl b/frontend-noscript/index.html.gotmpl new file mode 100644 index 0000000..9eed572 --- /dev/null +++ b/frontend-noscript/index.html.gotmpl @@ -0,0 +1,3 @@ + + + diff --git a/main.go b/main.go index 70a3298..94564fe 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "io" "os" "strings" @@ -8,13 +9,12 @@ import ( "github.com/rs/zerolog/log" "gitlab.com/mstarongitlab/linstrom/ap" "gitlab.com/mstarongitlab/linstrom/config" + "gitlab.com/mstarongitlab/linstrom/storage" + "gitlab.com/mstarongitlab/linstrom/storage/cache" ) func main() { - if *flagPrettyPrint { - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) - log.Info().Msg("Pretty logging enabled") - } + setLogger() setLogLevel() if err := config.ReadAndWriteToGlobal(*flagConfigFile); err != nil { log.Fatal(). @@ -22,12 +22,32 @@ func main() { Str("config-file", *flagConfigFile). Msg("Failed to read config and couldn't write default") } - // "@aufricus_athudath@activitypub.academy" res, err := ap.GetAccountWebfinger("@aufricus_athudath@activitypub.academy") log.Info(). Err(err). Any("webfinger", res). Msg("Webfinger request result for @aufricus_athudath@activitypub.academy") + storageCache, err := cache.NewCache( + config.GlobalConfig.Storage.MaxInMemoryCacheSize, + config.GlobalConfig.Storage.RedisUrl, + ) + if err != nil { + log.Fatal().Err(err).Msg("Failed to start cache") + } + var store *storage.Storage + if config.GlobalConfig.Storage.DbIsPostgres != nil && + *config.GlobalConfig.Storage.DbIsPostgres { + store, err = storage.NewStoragePostgres( + config.GlobalConfig.Storage.DatabaseUrl, + storageCache, + ) + } else { + store, err = storage.NewStorageSqlite(config.GlobalConfig.Storage.DatabaseUrl, storageCache) + } + if err != nil { + log.Fatal().Err(err).Msg("Failed to setup storage") + } + _ = store } func setLogLevel() { @@ -49,3 +69,17 @@ func setLogLevel() { zerolog.SetGlobalLevel(zerolog.InfoLevel) } } + +func setLogger(extraLogWriters ...io.Writer) { + if *flagPrettyPrint { + console := zerolog.ConsoleWriter{Out: os.Stderr} + log.Logger = zerolog.New(zerolog.MultiLevelWriter(append([]io.Writer{console}, extraLogWriters...)...)). + With(). + Timestamp(). + Logger() + } else { + log.Logger = zerolog.New(zerolog.MultiLevelWriter( + append([]io.Writer{log.Logger}, extraLogWriters...)..., + )).With().Timestamp().Logger() + } +} diff --git a/remoteStorage/remoteServer.go b/remoteStorage/remoteServer.go new file mode 100644 index 0000000..13b147e --- /dev/null +++ b/remoteStorage/remoteServer.go @@ -0,0 +1,13 @@ +package remotestorage + +import "gitlab.com/mstarongitlab/linstrom/storage" + +// Wrapper around db storage +// storage.Storage is for the db and cache access only, +// while this one wraps storage.Storage to also provide remote fetching of missing resources. +// So if an account doesn't exist in db or cache, this wrapper will attempt to fetch it +type RemoteStorage struct { + store *storage.Storage +} + +// TODO: Implement just about everything storage has, but with remote fetching if storage fails diff --git a/storage/cache.go b/storage/cache.go new file mode 100644 index 0000000..d1d5562 --- /dev/null +++ b/storage/cache.go @@ -0,0 +1,47 @@ +package storage + +import ( + "errors" + "strings" + + "github.com/redis/go-redis/v9" +) + +const ( + cacheUserHandleToIdPrefix = "acc-name-to-id:" + cacheUserIdToAccPrefix = "acc-id-to-data:" +) + +var errCacheNotFound = errors.New("not found in cache") + +// Find an account id in cache using a given user handle +func (s *Storage) cacheHandleToAccUid(handle string) (*string, error) { + // Where to put the data (in case it's found) + var target string + found, err := s.cache.Get(cacheUserHandleToIdPrefix+strings.TrimLeft(handle, "@"), &target) + // If nothing was found, check error + if !found { + // Case error is set and NOT redis' error for nothing found: Return that error + if err != nil && !errors.Is(err, redis.Nil) { + return nil, err + } else { + // Else return errCacheNotFound + return nil, errCacheNotFound + } + } + return &target, nil +} + +// Find an account's data in cache using a given account id +func (s *Storage) cacheAccIdToData(id string) (*Account, error) { + var target Account + found, err := s.cache.Get(cacheUserIdToAccPrefix+id, &target) + if !found { + if err != nil && !errors.Is(err, redis.Nil) { + return nil, err + } else { + return nil, errCacheNotFound + } + } + return &target, nil +} diff --git a/storage/cache/cache.go b/storage/cache/cache.go index 3886ca3..243f5b6 100644 --- a/storage/cache/cache.go +++ b/storage/cache/cache.go @@ -1,6 +1,8 @@ package cache import ( + "context" + "fmt" "time" "github.com/dgraph-io/ristretto" @@ -10,7 +12,6 @@ import ( ristretto_store "github.com/eko/gocache/store/ristretto/v4" "github.com/redis/go-redis/v9" "github.com/rs/zerolog/log" - "gitlab.com/mstarongitlab/linstrom/config" ) type Cache struct { @@ -19,16 +20,20 @@ type Cache struct { encoders *EncoderPool } -func NewCache() (*Cache, error) { +var ctxBackground = context.Background() + +// TODO: Maybe also include metrics +func NewCache(maxSize int64, redisUrl *string) (*Cache, error) { // ristretto is an in-memory cache + log.Debug().Int64("max-size", maxSize).Msg("Setting up ristretto") ristrettoCache, err := ristretto.NewCache(&ristretto.Config{ // The *10 is a recommendation from ristretto - NumCounters: config.GlobalConfig.Storage.MaxInMemoryCacheSize * 10, - MaxCost: config.GlobalConfig.Storage.MaxInMemoryCacheSize, + NumCounters: maxSize * 10, + MaxCost: maxSize, BufferItems: 64, // Same here }) if err != nil { - return nil, err + return nil, fmt.Errorf("ristretto cache error: %w", err) } ristrettoStore := ristretto_store.NewRistretto( ristrettoCache, @@ -36,12 +41,8 @@ func NewCache() (*Cache, error) { ) var cacheManager *cache.ChainCache[[]byte] - if config.GlobalConfig.Storage.UseRedis { - if config.GlobalConfig.Storage.RedisUrl == nil { - log.Fatal(). - Msg("Told to use redis in addition to in-memory store, but no redis url provided!") - } - opts, err := redis.ParseURL(*config.GlobalConfig.Storage.RedisUrl) + if redisUrl != nil { + opts, err := redis.ParseURL(*redisUrl) if err != nil { return nil, err } @@ -61,3 +62,29 @@ func NewCache() (*Cache, error) { encoders: NewEncoderPool(), }, nil } + +func (c *Cache) Get(key string, target any) (bool, error) { + data, err := c.cache.Get(ctxBackground, key) + if err != nil { + return false, err + } + err = c.decoders.Decode(data, target) + if err != nil { + return false, err + } + return true, err +} + +func (c *Cache) Set(key string, value any) error { + data, err := c.encoders.Encode(value) + if err != nil { + return err + } + err = c.cache.Set(ctxBackground, key, data, nil) + return err +} + +func (c *Cache) Delete(key string) { + // Error return doesn't matter here. Delete is delete is gone + _ = c.cache.Delete(ctxBackground, key) +} diff --git a/storage/cache/coderPools.go b/storage/cache/coderPools.go index 7172eb6..70244e1 100644 --- a/storage/cache/coderPools.go +++ b/storage/cache/coderPools.go @@ -95,7 +95,7 @@ func NewDecoderPool() *DecoderPool { } // Decode some value with gob -func (p *DecoderPool) Encode(raw []byte, target any) error { +func (p *DecoderPool) Decode(raw []byte, target any) error { var encoder *gobDecoder // First try to find an available encoder // Read only lock should be fine here since locks are atomic i diff --git a/storage/notes.go b/storage/notes.go index f5f14d9..7d4c3b2 100644 --- a/storage/notes.go +++ b/storage/notes.go @@ -14,7 +14,7 @@ type Note struct { // Soft delete means that this entry still exists in the db, but gorm won't include it anymore unless specifically told to // If not null, this entry is marked as deleted DeletedAt gorm.DeletedAt `gorm:"index"` - Creator string // Full handle of the creator, eg: @max@example.com + Creator string // Id of the author in the db, not the handle 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 @@ -29,3 +29,25 @@ type Note struct { OriginServer string // Url of the origin server. Also the primary key for those Tags []string `gorm:"serializer:json"` // Hashtags } + +func (s *Storage) FindNoteById(id string) (*Note, error) { + // TODO: Implement me + panic("not implemented") +} + +func (s *Storage) FindNotesByFuzzyContent(fuzzyContent string) ([]Note, error) { + // TODO: Implement me + panic("not implemented") +} + +func (s *Storage) FindNotesByAuthorHandle(handle string) ([]Note, error) { + // TODO: Implement me + panic("not implemented") +} + +func (s *Storage) FindNotesByAuthorId(id string) ([]Note, error) { + // TODO: Implement me + panic("not implemented") +} + +// Update, create, delete diff --git a/storage/remoteServerInfo.go b/storage/remoteServerInfo.go index 860cda7..48a4005 100644 --- a/storage/remoteServerInfo.go +++ b/storage/remoteServerInfo.go @@ -19,3 +19,32 @@ type RemoteServer struct { Icon string // ID of a media file IsSelf bool // Whether this server is yours truly } + +func (s *Storage) FindRemoteServer(url string) (*RemoteServer, error) { + // TODO: Implement me + panic("not implemented") +} + +// Find a remote server with a given display name +func (s *Storage) FindRemoteServerByDisplayName(displayName string) (*RemoteServer, error) { + // TODO: Implement me + panic("not implemented") +} + +// Create a new remote server +func (s *Storage) NewRemoteServer( + url, displayName, icon string, + serverType RemoteServerType, +) (*RemoteServer, error) { + // TODO: Implement me + panic("not implemented") +} + +// Update a remote server with the given url +// If displayName is set, update that +// If icon is set, update that +// Returns the updated version +func (s *Storage) UpdateRemoteServer(url string, displayName, icon *string) (*RemoteServer, error) { + // TODO: Implement me + panic("not implemented") +} diff --git a/storage/storage.go b/storage/storage.go index 1d2d8c8..3696a24 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,7 +1,10 @@ package storage import ( + "errors" + "github.com/glebarez/sqlite" + "gitlab.com/mstarongitlab/linstrom/storage/cache" "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -9,30 +12,33 @@ import ( // Storage is responsible for all database, cache and media related actions // and serves as the lowest layer of the cake type Storage struct { - db *gorm.DB + db *gorm.DB + cache *cache.Cache } +var ErrInvalidData = errors.New("invalid data") + // Build a new storage using sqlite as database backend -func NewStorageSqlite(filePath string) (*Storage, error) { +func NewStorageSqlite(filePath string, cache *cache.Cache) (*Storage, error) { db, err := gorm.Open(sqlite.Open(filePath)) if err != nil { return nil, err } - return storageFromEmptyDb(db) + return storageFromEmptyDb(db, cache) } -func NewStoragePostgres(dbUrl string) (*Storage, error) { +func NewStoragePostgres(dbUrl string, cache *cache.Cache) (*Storage, error) { db, err := gorm.Open(postgres.Open(dbUrl)) if err != nil { return nil, err } - return storageFromEmptyDb(db) + return storageFromEmptyDb(db, cache) } -func storageFromEmptyDb(db *gorm.DB) (*Storage, error) { +func storageFromEmptyDb(db *gorm.DB, cache *cache.Cache) (*Storage, error) { // AutoMigrate ensures the db is in a state where all the structs given here // have their own tables and relations setup. It also updates tables if necessary - db.AutoMigrate( + err := db.AutoMigrate( MediaFile{}, Account{}, RemoteServer{}, @@ -40,9 +46,13 @@ func storageFromEmptyDb(db *gorm.DB) (*Storage, error) { Role{}, PasskeySession{}, ) + if err != nil { + return nil, err + } // And finally, build the actual storage struct return &Storage{ - db: db, + db: db, + cache: cache, }, nil } diff --git a/storage/user.go b/storage/user.go index 47a3590..43cd979 100644 --- a/storage/user.go +++ b/storage/user.go @@ -3,6 +3,7 @@ package storage import ( "crypto/rand" "errors" + "strings" "time" "github.com/go-webauthn/webauthn/webauthn" @@ -94,6 +95,23 @@ type RemoteAccountLinks struct { func (s *Storage) FindAccountByFullHandle(handle string) (*Account, error) { log.Trace().Caller().Send() log.Debug().Str("account-handle", handle).Msg("Looking for account by handle") + log.Debug().Str("account-handle", handle).Msg("Checking if there's a cache hit") + + // Try and find the account in cache first + cacheAccId, err := s.cacheHandleToAccUid(handle) + if err == nil { + log.Info().Str("account-handle", handle).Msg("Hit account handle in cache") + // Then always load via id since unique key access should be faster than string matching + return s.FindAccountById(*cacheAccId) + } else { + if !errors.Is(err, errCacheNotFound) { + log.Error().Err(err).Str("account-handle", handle).Msg("Problem while checking cache for account") + return nil, err + } + } + + // Failed to find in cache, go the slow route of hitting the db + log.Debug().Str("account-handle", handle).Msg("Didn't hit account in cache, going to db") name, server, err := ap.SplitFullHandle(handle) if err != nil { log.Warn().Err(err).Str("account-handle", handle).Msg("Failed to split up account handle") @@ -110,7 +128,19 @@ func (s *Storage) FindAccountByFullHandle(handle string) (*Account, error) { } return nil, res.Error } - log.Info().Str("account-handle", handle).Msg("Found account") + log.Info().Str("account-handle", handle).Msg("Found account, also inserting into cache") + if err = s.cache.Set(cacheUserIdToAccPrefix+acc.ID, &acc); err != nil { + log.Warn(). + Err(err). + Str("account-handle", handle). + Msg("Found account but failed to insert into cache") + } + if err = s.cache.Set(cacheUserHandleToIdPrefix+strings.TrimLeft(handle, "@"), acc.ID); err != nil { + log.Warn(). + Err(err). + Str("account-handle", handle). + Msg("Failed to store handle to id in cache") + } return &acc, nil } @@ -118,8 +148,18 @@ func (s *Storage) FindAccountByFullHandle(handle string) (*Account, error) { func (s *Storage) FindAccountById(id string) (*Account, error) { log.Trace().Caller().Send() log.Debug().Str("account-id", id).Msg("Looking for account by id") - acc := Account{} - res := s.db.First(&acc, id) + log.Debug().Str("account-id", id).Msg("First trying to hit cache") + acc, err := s.cacheAccIdToData(id) + if err == nil { + log.Info().Str("account-id", id).Msg("Found account in cache") + return acc, nil + } else if !errors.Is(err, errCacheNotFound) { + log.Error().Err(err).Str("account-id", id).Msg("Error while looking for account in cache") + return nil, err + } + + log.Debug().Str("account-id", id).Msg("Didn't hit account in cache, checking db") + res := s.db.First(acc, id) if res.Error != nil { if errors.Is(res.Error, gorm.ErrRecordNotFound) { log.Warn().Str("account-id", id).Msg("Account not found") @@ -128,8 +168,27 @@ func (s *Storage) FindAccountById(id string) (*Account, error) { } return nil, res.Error } - log.Info().Str("account-id", id).Msg("Found account") - return &acc, nil + log.Info().Str("account-id", id).Msg("Found account in db, also adding to cache") + if err = s.cache.Set(cacheUserIdToAccPrefix+id, acc); err != nil { + log.Warn().Err(err).Str("account-id", id).Msg("Failed to add account to cache") + } + return acc, nil +} + +// Update a given account in storage and cache +func (s *Storage) UpdateAccount(acc *Account) error { + // If the account is nil or doesn't have an id, error out + if acc == nil || acc.ID == "" { + return ErrInvalidData + } + res := s.db.Save(acc) + if res.Error != nil { + return res.Error + } + if err := s.cache.Set(cacheUserIdToAccPrefix+acc.ID, acc); err != nil { + return err + } + return nil } func (s *Storage) NewEmptyAccount() (*Account, error) { From 07d98d1ef5a4f4ef8fb0d0197baafc37153c113e Mon Sep 17 00:00:00 2001 From: mStar Date: Thu, 12 Sep 2024 16:57:53 +0200 Subject: [PATCH 07/13] Moew weork done --- outgoingEventQueue/queue.go | 1 + storage/cache.go | 30 ++++++++++++- storage/errors.go | 8 +++- storage/mediaFile.go | 15 +++++-- storage/noteTargets.go | 6 ++- storage/notes.go | 86 +++++++++++++++++++++++++++++++++---- storage/passkeySessions.go | 9 ++++ storage/serverTypes.go | 3 ++ storage/user.go | 4 ++ storage/userInfoFields.go | 1 + 10 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 outgoingEventQueue/queue.go diff --git a/outgoingEventQueue/queue.go b/outgoingEventQueue/queue.go new file mode 100644 index 0000000..12e524f --- /dev/null +++ b/outgoingEventQueue/queue.go @@ -0,0 +1 @@ +package outgoingeventqueue diff --git a/storage/cache.go b/storage/cache.go index d1d5562..789d7fc 100644 --- a/storage/cache.go +++ b/storage/cache.go @@ -7,15 +7,21 @@ import ( "github.com/redis/go-redis/v9" ) +// various prefixes for accessing items in the cache (since it's a simple key-value store) const ( cacheUserHandleToIdPrefix = "acc-name-to-id:" cacheUserIdToAccPrefix = "acc-id-to-data:" + cacheNoteIdToNotePrefix = "note-id-to-data:" ) +// An error describing the case where some value was just not found in the cache var errCacheNotFound = errors.New("not found in cache") // Find an account id in cache using a given user handle -func (s *Storage) cacheHandleToAccUid(handle string) (*string, error) { +// accId contains the Id of the account if found +// err contains an error describing why an account's id couldn't be found +// The most common one should be errCacheNotFound +func (s *Storage) cacheHandleToAccUid(handle string) (accId *string, err error) { // Where to put the data (in case it's found) var target string found, err := s.cache.Get(cacheUserHandleToIdPrefix+strings.TrimLeft(handle, "@"), &target) @@ -33,7 +39,10 @@ func (s *Storage) cacheHandleToAccUid(handle string) (*string, error) { } // Find an account's data in cache using a given account id -func (s *Storage) cacheAccIdToData(id string) (*Account, error) { +// acc contains the full account as stored last time if found +// err contains an error describing why an account couldn't be found +// The most common one should be errCacheNotFound +func (s *Storage) cacheAccIdToData(id string) (acc *Account, err error) { var target Account found, err := s.cache.Get(cacheUserIdToAccPrefix+id, &target) if !found { @@ -45,3 +54,20 @@ func (s *Storage) cacheAccIdToData(id string) (*Account, error) { } return &target, nil } + +// Find a cached note given its ID +// note contains the full note as stored last time if found +// err contains an error describing why a note couldn't be found +// The most common one should be errCacheNotFound +func (s *Storage) cacheNoteIdToData(id string) (note *Note, err error) { + target := Note{} + found, err := s.cache.Get(cacheNoteIdToNotePrefix+id, &target) + if !found { + if err != nil && !errors.Is(err, redis.Nil) { + return nil, err + } else { + return nil, errCacheNotFound + } + } + return &target, nil +} diff --git a/storage/errors.go b/storage/errors.go index a9bc1e1..ac1ee99 100644 --- a/storage/errors.go +++ b/storage/errors.go @@ -1,7 +1,11 @@ package storage -type NotImplementedError struct{} +import "errors" -func (n NotImplementedError) Error() string { +type ErrNotImplemented struct{} + +func (n ErrNotImplemented) Error() string { return "Not implemented yet" } + +var ErrEntryNotFound = errors.New("entry not found") diff --git a/storage/mediaFile.go b/storage/mediaFile.go index 7ae3cae..c3a55c3 100644 --- a/storage/mediaFile.go +++ b/storage/mediaFile.go @@ -6,6 +6,10 @@ import ( "gorm.io/gorm" ) +// MediaFile represents a file containing some media +// This media may be stored locally via a file system or S3 bucket +// or remote on a different server +// Additionally, it contains some useful data for more easily working with it type MediaFile struct { ID string `gorm:"primarykey"` // The unique ID of this media file CreatedAt time.Time // When this entry was created @@ -15,13 +19,18 @@ type MediaFile struct { // If not null, this entry is marked as deleted DeletedAt gorm.DeletedAt `gorm:"index"` Remote bool // whether the attachment is a remote one - Link string // url if remote attachment, identifier if local - Type string // What media type this is, eg image/png + // Always an url, either an absolute path to a local file or an url to a remote file + Link string + Type string // What media type this is following mime types, eg image/png // Whether this media has been cached locally - // Only really used for user and server icons, not attachments + // Used for user and server icons as well as emotes, not attachments // If true, Link will be read as file path. url otherwise // 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 + // TODO: Decide later during usage if attachment caching would be a good idea LocallyCached bool + // Descriptive name for a media file + // Emote name for example or servername.filetype for a server's icon + Name string } diff --git a/storage/noteTargets.go b/storage/noteTargets.go index 50b8abf..5fcdc82 100644 --- a/storage/noteTargets.go +++ b/storage/noteTargets.go @@ -5,6 +5,10 @@ import ( "errors" ) +// For pretty printing during debug +// If `go generate` is run, it'll generate the necessary function and data for pretty printing +//go:generate stringer -type NoteTarget + // What feed a note is targeting (public, home, followers or dm) type NoteTarget uint8 @@ -14,7 +18,7 @@ const ( // The note is intended only for the home screen // not really any idea what the difference is compared to public // Maybe home notes don't show up on the server feed but still for everyone's home feed if it reaches them via follow or boost - NOTE_TARGET_HOME = NoteTarget(1 << iota) + NOTE_TARGET_HOME = NoteTarget(1 << iota) // The note is intended only for followers NOTE_TARGET_FOLLOWERS // The note is intended only for a DM to one or more targets diff --git a/storage/notes.go b/storage/notes.go index 7d4c3b2..3809429 100644 --- a/storage/notes.go +++ b/storage/notes.go @@ -1,11 +1,19 @@ package storage import ( + "fmt" "time" + "github.com/rs/zerolog/log" "gorm.io/gorm" ) +// Note represents an ActivityPub note +// ActivityPub notes can be quite a few things, depending on fields provided. +// A survey, a reply, a quote of another note, etc +// And depending on the origin server of a note, they are treated differently +// with for example rendering or available actions +// This struct attempts to contain all information necessary for easily working with a note type Note struct { ID string `gorm:"primarykey"` // Make ID a string (uuid) for other implementations CreatedAt time.Time // When this entry was created @@ -31,23 +39,85 @@ type Note struct { } func (s *Storage) FindNoteById(id string) (*Note, error) { - // TODO: Implement me - panic("not implemented") + note := &Note{} + cacheNote, err := s.cacheNoteIdToData(id) + switch err { + case nil: + return cacheNote, nil + // Empty case, not found in cache means check db + case errCacheNotFound: + default: + return nil, err + } + switch err { + + } + err = s.db.Find(note, id).Error + switch err { + case nil: + if err = s.cache.Set(cacheNoteIdToNotePrefix+id, note); err != nil { + log.Warn().Err(err).Str("note-id", id).Msg("Failed to place note in cache") + } + return note, nil + case gorm.ErrRecordNotFound: + return nil, ErrEntryNotFound + default: + return nil, err + } } func (s *Storage) FindNotesByFuzzyContent(fuzzyContent string) ([]Note, error) { - // TODO: Implement me - panic("not implemented") + notes := []Note{} + // TODO: Figure out if cache can be used here too + err := s.db.Where("raw_content LIKE %?%", fuzzyContent).Find(notes).Error + if err != nil { + return nil, err + } + return notes, nil } func (s *Storage) FindNotesByAuthorHandle(handle string) ([]Note, error) { - // TODO: Implement me - panic("not implemented") + acc, err := s.FindAccountByFullHandle(handle) + if err != nil { + return nil, fmt.Errorf("account with handle %s not found: %w", handle, err) + } + return s.FindNotesByAuthorId(acc.ID) } func (s *Storage) FindNotesByAuthorId(id string) ([]Note, error) { - // TODO: Implement me + notes := []Note{} + err := s.db.Where("creator = ?", id).Find(notes).Error + switch err { + case nil: + return notes, nil + case gorm.ErrRecordNotFound: + return nil, ErrEntryNotFound + default: + return nil, err + } +} + +func (s *Storage) UpdateNote(note *Note) error { + if note == nil || note.ID == "" { + return ErrInvalidData + } + err := s.db.Save(note).Error + if err != nil { + return err + } + err = s.cache.Set(cacheNoteIdToNotePrefix+note.ID, note) + if err != nil { + log.Warn().Err(err).Msg("Failed to update note into cache. Cache and db might be out of sync, a force sync is recommended") + } + return nil +} + +func (s *Storage) CreateNote() (*Note, error) { + // TODO: Think of good arguments and implement me panic("not implemented") } -// Update, create, delete +func (s *Storage) DeleteNote(id string) { + s.cache.Delete(cacheNoteIdToNotePrefix + id) + s.db.Delete(Note{ID: id}) +} diff --git a/storage/passkeySessions.go b/storage/passkeySessions.go index aaba918..71dfb6a 100644 --- a/storage/passkeySessions.go +++ b/storage/passkeySessions.go @@ -6,6 +6,9 @@ import ( "github.com/rs/zerolog/log" ) +// Session data used during login attempts with a passkey +// Not actually used afterwards to verify a normal session +// NOTE: Doesn't contain a DeletedAt field, thus deletions are automatically hard and not reversible type PasskeySession struct { ID string `gorm:"primarykey"` Data webauthn.SessionData `gorm:"serializer:json"` @@ -13,12 +16,15 @@ type PasskeySession struct { // ---- Section SessionStore +// Generate some id for a new session. Just returns a new uuid func (s *Storage) GenSessionID() (string, error) { x := uuid.NewString() log.Debug().Str("session-id", x).Msg("Generated new passkey session id") return x, nil } +// Look for an active session with a given id +// Returns the session if found and a bool indicating if a session was found func (s *Storage) GetSession(sessionId string) (*webauthn.SessionData, bool) { log.Debug().Str("id", sessionId).Msg("Looking for passkey session") session := PasskeySession{} @@ -30,6 +36,7 @@ func (s *Storage) GetSession(sessionId string) (*webauthn.SessionData, bool) { return &session.Data, true } +// Save (or update) a session with the new data func (s *Storage) SaveSession(token string, data *webauthn.SessionData) { log.Debug().Str("id", token).Any("webauthn-data", data).Msg("Saving passkey session") session := PasskeySession{ @@ -39,6 +46,8 @@ func (s *Storage) SaveSession(token string, data *webauthn.SessionData) { s.db.Save(&session) } +// Delete a session +// NOTE: This is a hard delete since the session struct contains no DeletedAt field func (s *Storage) DeleteSession(token string) { log.Debug().Str("id", token).Msg("Deleting passkey session (if one exists)") s.db.Delete(&PasskeySession{ID: token}) diff --git a/storage/serverTypes.go b/storage/serverTypes.go index 111dd85..f69166c 100644 --- a/storage/serverTypes.go +++ b/storage/serverTypes.go @@ -5,6 +5,9 @@ import ( "errors" ) +// TODO: Decide whether to turn this into an int too to save resources +// And then use go:generate instead for pretty printing + // What software a server is running // Mostly important for rendering type RemoteServerType string diff --git a/storage/user.go b/storage/user.go index 43cd979..8ec499c 100644 --- a/storage/user.go +++ b/storage/user.go @@ -191,6 +191,7 @@ func (s *Storage) UpdateAccount(acc *Account) error { return nil } +// Create a new empty account for future use func (s *Storage) NewEmptyAccount() (*Account, error) { log.Trace().Caller().Send() log.Debug().Msg("Creating new empty account") @@ -219,6 +220,9 @@ func (s *Storage) NewEmptyAccount() (*Account, error) { return &acc, nil } +// Create a new local account using the given handle +// The handle in this case is only the part before the domain (example: @bob@example.com would have a handle of bob) +// It also sets up a bunch of values that tend to be obvious for local accounts func (s *Storage) NewLocalAccount(handle string) (*Account, error) { log.Trace().Caller().Send() log.Debug().Str("account-handle", handle).Msg("Creating new local account") diff --git a/storage/userInfoFields.go b/storage/userInfoFields.go index a3dc5ea..fa2915f 100644 --- a/storage/userInfoFields.go +++ b/storage/userInfoFields.go @@ -6,6 +6,7 @@ import ( "gorm.io/gorm" ) +// Describes a custom attribute field for accounts type UserInfoField struct { gorm.Model // Can actually just embed this as is here as those are not something directly exposed :3 Name string From 7ec0ce5a450e54b7785c4bd2da1c59a63e7cd74a Mon Sep 17 00:00:00 2001 From: mStar Date: Fri, 13 Sep 2024 09:18:05 +0200 Subject: [PATCH 08/13] blegh --- storage/mediaFile.go | 35 +++++++++++++++++++---------------- storage/storage.go | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/storage/mediaFile.go b/storage/mediaFile.go index c3a55c3..a37e1e2 100644 --- a/storage/mediaFile.go +++ b/storage/mediaFile.go @@ -6,11 +6,10 @@ import ( "gorm.io/gorm" ) -// MediaFile represents a file containing some media -// This media may be stored locally via a file system or S3 bucket -// or remote on a different server -// Additionally, it contains some useful data for more easily working with it -type MediaFile struct { +// MediaMetadata contains metadata about some media +// Metadata includes whether it's a remote file or not, what the name is, +// the MIME type, and an identifier pointing to its location +type MediaMetadata struct { ID string `gorm:"primarykey"` // The unique ID of this media file CreatedAt time.Time // When this entry was created UpdatedAt time.Time // When this entry was last updated @@ -19,18 +18,22 @@ type MediaFile struct { // If not null, this entry is marked as deleted DeletedAt gorm.DeletedAt `gorm:"index"` Remote bool // whether the attachment is a remote one - // Always an url, either an absolute path to a local file or an url to a remote file - Link string - Type string // What media type this is following mime types, eg image/png - // Whether this media has been cached locally - // Used for user and server icons as well as emotes, not attachments - // If true, Link will be read as file path. url otherwise - // 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 - // TODO: Decide later during usage if attachment caching would be a good idea - LocallyCached bool + // Where the media is stored. Url if remote file, + Location string + Type string // What media type this is following mime types, eg image/png // Descriptive name for a media file // Emote name for example or servername.filetype for a server's icon Name string } + +// TODO: Figure out how to actually manage media. Because this current idea sucks +// One idea would be to make another storage provider, but purely focused on handling the files +// and then using this section to store metadata about the files it knows +func (s *Storage) NewMediaMetadata(url, mediaType, name string) (*MediaMetadata, error) { + newMedia := MediaMetadata{ + Location: url, + Name: name, + } + s.db.Create(&newMedia) + return nil, nil +} diff --git a/storage/storage.go b/storage/storage.go index 3696a24..92ce234 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -39,7 +39,7 @@ func storageFromEmptyDb(db *gorm.DB, cache *cache.Cache) (*Storage, error) { // AutoMigrate ensures the db is in a state where all the structs given here // have their own tables and relations setup. It also updates tables if necessary err := db.AutoMigrate( - MediaFile{}, + MediaMetadata{}, Account{}, RemoteServer{}, Note{}, From 28c5f546fe4947c28afe73ce4150a6f4477b070f Mon Sep 17 00:00:00 2001 From: mStar Date: Fri, 13 Sep 2024 11:58:29 +0200 Subject: [PATCH 09/13] Updates stuff --- future-useful-libs.md | 6 ++++ go.mod | 10 +++--- go.sum | 10 +++--- storage/mediaFile.go | 48 +++++++++++++++++++++++--- storage/mediaProvider/mediaProvider.go | 1 + storage/storage.go | 5 +++ 6 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 future-useful-libs.md create mode 100644 storage/mediaProvider/mediaProvider.go diff --git a/future-useful-libs.md b/future-useful-libs.md new file mode 100644 index 0000000..2869049 --- /dev/null +++ b/future-useful-libs.md @@ -0,0 +1,6 @@ +- golang.org/x/net/html + - Parsing and manipulating html +- https://github.com/chasefleming/elem-go + - Generating html +- mime + - working with mime types diff --git a/go.mod b/go.mod index 887ecde..537442f 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,14 @@ toolchain go1.23.0 require ( github.com/BurntSushi/toml v1.4.0 - github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 + github.com/dgraph-io/ristretto v0.1.1 github.com/eko/gocache/lib/v4 v4.1.6 - github.com/eko/gocache/store/memcache/v4 v4.2.2 + github.com/eko/gocache/store/redis/v4 v4.2.2 + github.com/eko/gocache/store/ristretto/v4 v4.2.2 github.com/glebarez/sqlite v1.11.0 github.com/go-webauthn/webauthn v0.11.2 github.com/mstarongithub/passkey v0.0.0-20240817142622-de6912c8303e + github.com/redis/go-redis/v9 v9.0.2 github.com/rs/zerolog v1.33.0 gitlab.com/mstarongitlab/goutils v1.3.0 gorm.io/driver/postgres v1.5.7 @@ -21,10 +23,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/eko/gocache/store/redis/v4 v4.2.2 // indirect - github.com/eko/gocache/store/ristretto/v4 v4.2.2 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -36,7 +35,6 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect - github.com/redis/go-redis/v9 v9.0.2 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/sync v0.8.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index ef84456..4bdcc70 100644 --- a/go.sum +++ b/go.sum @@ -44,11 +44,12 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 h1:wAIE/kN63Oig1DdOzN7O+k4AbFh2cCJoKMFXrwRJtzk= -github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ= +github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8= +github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -66,6 +67,7 @@ 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/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= @@ -74,8 +76,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eko/gocache/lib/v4 v4.1.6 h1:5WWIGISKhE7mfkyF+SJyWwqa4Dp2mkdX8QsZpnENqJI= github.com/eko/gocache/lib/v4 v4.1.6/go.mod h1:HFxC8IiG2WeRotg09xEnPD72sCheJiTSr4Li5Ameg7g= -github.com/eko/gocache/store/memcache/v4 v4.2.2 h1:VKfxytQ5bkcfF3LhmgkrqRiEU2yCN2/rJBUvF1fKZJw= -github.com/eko/gocache/store/memcache/v4 v4.2.2/go.mod h1:9lFU3tZPiej8E3J4ueZ0K9kIdiDQpRxu6WhtId5OsZA= github.com/eko/gocache/store/redis/v4 v4.2.2 h1:Thw31fzGuH3WzJywsdbMivOmP550D6JS7GDHhvCJPA0= github.com/eko/gocache/store/redis/v4 v4.2.2/go.mod h1:LaTxLKx9TG/YUEybQvPMij++D7PBTIJ4+pzvk0ykz0w= github.com/eko/gocache/store/ristretto/v4 v4.2.2 h1:lXFzoZ5ck6Gy6ON7f5DHSkNt122qN7KoroCVgVwF7oo= diff --git a/storage/mediaFile.go b/storage/mediaFile.go index a37e1e2..5725aac 100644 --- a/storage/mediaFile.go +++ b/storage/mediaFile.go @@ -26,14 +26,52 @@ type MediaMetadata struct { Name string } -// TODO: Figure out how to actually manage media. Because this current idea sucks -// One idea would be to make another storage provider, but purely focused on handling the files -// and then using this section to store metadata about the files it knows -func (s *Storage) NewMediaMetadata(url, mediaType, name string) (*MediaMetadata, error) { +func (s *Storage) NewMediaMetadata(location, mediaType, name string) (*MediaMetadata, error) { newMedia := MediaMetadata{ - Location: url, + Location: location, Name: name, + Type: mediaType, } s.db.Create(&newMedia) return nil, nil } + +func (s *Storage) FuzzyFindMediaMetadataByName(name string) ([]MediaMetadata, error) { + notes := []MediaMetadata{} + err := s.db.Where("name LIKE %?%", name).Find(notes).Error + if err != nil { + return nil, err + } + return notes, nil +} + +func (s *Storage) GetMediaMetadataById(id string) (*MediaMetadata, error) { + media := MediaMetadata{ID: id} + err := s.db.First(&media).Error + if err != nil { + return nil, err + } + return &media, nil +} + +func (s *Storage) FuzzyFindMediaMetadataByLocation(location string) ([]MediaMetadata, error) { + data := []MediaMetadata{} + if err := s.db.Where("location LIKE %?%", location).Find(data).Error; err != nil { + return nil, err + } + return data, nil +} + +func (s *Storage) DeleteMediaMetadataById(id string) error { + return s.db.Delete(MediaMetadata{ID: id}).Error +} + +func (s *Storage) DeleteMediaMetadataByFuzzyLocation(location string) error { + var tmp MediaMetadata + return s.db.Where("location LIKE %?%", location).Delete(&tmp).Error +} + +func (s *Storage) DeleteMediaMetadataByFuzzyName(name string) error { + var tmp MediaMetadata + return s.db.Where("name LIKE %?%", name).Delete(&tmp).Error +} diff --git a/storage/mediaProvider/mediaProvider.go b/storage/mediaProvider/mediaProvider.go new file mode 100644 index 0000000..a2026c8 --- /dev/null +++ b/storage/mediaProvider/mediaProvider.go @@ -0,0 +1 @@ +package mediaprovider diff --git a/storage/storage.go b/storage/storage.go index 92ce234..d609588 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,3 +1,8 @@ +// TODO: Unify function names + +// Storage is the handler for cache and db access +// It handles storing various data in the database as well as caching that data +// Said data includes notes, accounts, metadata about media files, servers and similar package storage import ( From f69480b6b445ae29188a358cc448d580f942efb1 Mon Sep 17 00:00:00 2001 From: mStar Date: Fri, 13 Sep 2024 15:02:32 +0200 Subject: [PATCH 10/13] More stuff, add stuff, add todos --- Containerfile | 1 + frontend-noscript/index.html | 11 +++++++++++ frontend-noscript/index.html.gotmpl | 3 --- future-useful-libs.md | 2 ++ main.go | 5 +++++ outgoingEventQueue/queue.go | 9 +++++++++ .../remoteServer}/remoteServer.go | 0 storage/cache/coderPools.go | 4 +++- storage/mediaProvider/mediaProvider.go | 2 ++ storage/remoteUser.go | 2 ++ storage/roles.go | 3 +++ storage/userInfoFields.go | 2 ++ 12 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 Containerfile create mode 100644 frontend-noscript/index.html delete mode 100644 frontend-noscript/index.html.gotmpl rename {remoteStorage => server/remoteServer}/remoteServer.go (100%) diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..d4f5c0d --- /dev/null +++ b/Containerfile @@ -0,0 +1 @@ +# TODO: Implement me diff --git a/frontend-noscript/index.html b/frontend-noscript/index.html new file mode 100644 index 0000000..6c25dc5 --- /dev/null +++ b/frontend-noscript/index.html @@ -0,0 +1,11 @@ + + + + + + diff --git a/frontend-noscript/index.html.gotmpl b/frontend-noscript/index.html.gotmpl deleted file mode 100644 index 9eed572..0000000 --- a/frontend-noscript/index.html.gotmpl +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/future-useful-libs.md b/future-useful-libs.md index 2869049..f96b7fc 100644 --- a/future-useful-libs.md +++ b/future-useful-libs.md @@ -4,3 +4,5 @@ - Generating html - mime - working with mime types +- https://github.com/tursodatabase/go-libsql + - sqlite but probably better (and should also be able to compile to a static binary) diff --git a/main.go b/main.go index 94564fe..0181f58 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,4 @@ +// TODO: Add EUPL banner everywhere package main import ( @@ -48,6 +49,10 @@ func main() { log.Fatal().Err(err).Msg("Failed to setup storage") } _ = store + // TODO: Set up media server + // TODO: Set up http server + // TODO: Set up plugins + // TODO: Start everything } func setLogLevel() { diff --git a/outgoingEventQueue/queue.go b/outgoingEventQueue/queue.go index 12e524f..835e230 100644 --- a/outgoingEventQueue/queue.go +++ b/outgoingEventQueue/queue.go @@ -1 +1,10 @@ package outgoingeventqueue + +// TODO: Implement me + +/* +Queue for controlled distribution of outgoing events, such as note creations, deletions or updates +Also has to manage the case where an instance can't be reached or where Linstrom has to shut down +In case of a shutdown, it has to store all remaining tasks in the db to try again later after the next boot +Should preferably also be able to tell when a server is just gone and stop bothering about it +*/ diff --git a/remoteStorage/remoteServer.go b/server/remoteServer/remoteServer.go similarity index 100% rename from remoteStorage/remoteServer.go rename to server/remoteServer/remoteServer.go diff --git a/storage/cache/coderPools.go b/storage/cache/coderPools.go index 70244e1..7003939 100644 --- a/storage/cache/coderPools.go +++ b/storage/cache/coderPools.go @@ -42,7 +42,9 @@ func (p *EncoderPool) Encode(raw any) ([]byte, error) { defer encoder.Unlock() // Clear the buffer to avoid funky output from previous operations encoder.Buffer.Reset() - encoder.Encoder.Encode(raw) + if err := encoder.Encoder.Encode(raw); err != nil { + return nil, err + } data, err := io.ReadAll(encoder.Buffer) if err != nil { return nil, err diff --git a/storage/mediaProvider/mediaProvider.go b/storage/mediaProvider/mediaProvider.go index a2026c8..62157aa 100644 --- a/storage/mediaProvider/mediaProvider.go +++ b/storage/mediaProvider/mediaProvider.go @@ -1 +1,3 @@ package mediaprovider + +// TODO: Implement me diff --git a/storage/remoteUser.go b/storage/remoteUser.go index 2157ae9..c2bad26 100644 --- a/storage/remoteUser.go +++ b/storage/remoteUser.go @@ -1,5 +1,7 @@ package storage +// TODO: More helper stuff + func (s *Storage) NewRemoteUser(fullHandle string) (*Account, error) { return nil, nil } diff --git a/storage/roles.go b/storage/roles.go index 55d8c31..ace6e23 100644 --- a/storage/roles.go +++ b/storage/roles.go @@ -5,4 +5,7 @@ type Role struct { Name string // If set, counts as all permissions being set and all restrictions being disabled FullAdmin bool + // TODO: More control options + // Extend upon whatever Masto, Akkoma and Misskey have + // Lots of details please } diff --git a/storage/userInfoFields.go b/storage/userInfoFields.go index fa2915f..59d95b3 100644 --- a/storage/userInfoFields.go +++ b/storage/userInfoFields.go @@ -13,3 +13,5 @@ type UserInfoField struct { Value string LastUrlCheckDate *time.Time // Used if the value is an url to somewhere. Empty if value is not an url } + +// TODO: Add functions to store, load, update and delete these From d6635bb34da169751957b3c8c800630dfec30860 Mon Sep 17 00:00:00 2001 From: mStar Date: Fri, 13 Sep 2024 15:22:11 +0200 Subject: [PATCH 11/13] More comments for what to do --- outgoingEventQueue/queue.go | 8 ++++++++ storage/housekeeping.go | 6 ++++++ 2 files changed, 14 insertions(+) create mode 100644 storage/housekeeping.go diff --git a/outgoingEventQueue/queue.go b/outgoingEventQueue/queue.go index 835e230..14c2291 100644 --- a/outgoingEventQueue/queue.go +++ b/outgoingEventQueue/queue.go @@ -7,4 +7,12 @@ Queue for controlled distribution of outgoing events, such as note creations, de Also has to manage the case where an instance can't be reached or where Linstrom has to shut down In case of a shutdown, it has to store all remaining tasks in the db to try again later after the next boot Should preferably also be able to tell when a server is just gone and stop bothering about it + + +Implementation idea: New job gets send to queue (via function call). Queue then launches a new goroutine for that job +Goroutine then launches multiple new goroutines, one per targeted inbox +Each of those will wait for a ticker or a stop signal +On each ticker they'll try to deliver the payload to the target inbox +If a stop signal comes in, push the payload and goal into the db to pick up again later +On stop or sucessful delivery, exit the goroutine */ diff --git a/storage/housekeeping.go b/storage/housekeeping.go new file mode 100644 index 0000000..aa7a815 --- /dev/null +++ b/storage/housekeeping.go @@ -0,0 +1,6 @@ +package storage + +// Contains various functions for housekeeping +// Things like true deletion of soft deleted data after some time +// Or removing inactive access tokens +// All of this will be handled by goroutines From efc0b2e28e7125fd3695a96c26ed3bf8127351da Mon Sep 17 00:00:00 2001 From: mStar Date: Fri, 13 Sep 2024 16:31:15 +0200 Subject: [PATCH 12/13] Expand and prepare container file --- Containerfile | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Containerfile b/Containerfile index d4f5c0d..554588f 100644 --- a/Containerfile +++ b/Containerfile @@ -1 +1,26 @@ -# TODO: Implement me +FROM docker.io/node:22 AS build-ember +# TODO: Implement ember frontend build +FROM docker.io/golang:1.23 AS build-go +# TODO: Implement golang build +FROM gcr.io/distroless/static-debian12:latest as release +# TODO: Implement final release container + +# OCI labels, following https://github.com/opencontainers/image-spec/blob/main/annotations.md + +# Make this one dynamic +# LABEL org.opencontainers.image.created = rfc timestamp, according to https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 +LABEL org.opencontainers.image.authors="Melody Becker/m*" +# LABEL org.opencontainers.image.url="Main linstrom page url here" +# LABEL org.opencontainers.image.documentation="Url to main linstrom documentation here" +LABEL org.opencontainers.image.source="https://gitlab.com/mstarongitlab/linstrom" +# Make this one dynamic from build tag +LABEL org.opencontainers.image.version="0.0.1" +# LABEL org.opencontainers.image.revision="idk what" +# LABEL org.opencontainers.image.vendor="None/m*" +LABEL org.opencontainers.image.licenses="EUPL-1.2" +# LABEL org.opencontainers.image.ref.name="uhh, no idea either" +LABEL org.opencontainers.image.title="Linstrom" +LABEL org.opencontainers.image.description="A social media server acting as part of the fediverse" +# Make this automatic since latest distroless image gets update constantly +# LABEL org.opencontainers.image.base.digest="digest/hash of base image" +LABEL org.opencontainers.image.base.name="gcr.io/distroless/static-debian12:latest" From 0ec0ad42bca3a05b05256862904285bb74e314cb Mon Sep 17 00:00:00 2001 From: mStar Date: Fri, 13 Sep 2024 16:58:22 +0200 Subject: [PATCH 13/13] more comment work --- outgoingEventQueue/queue.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/outgoingEventQueue/queue.go b/outgoingEventQueue/queue.go index 14c2291..51974c1 100644 --- a/outgoingEventQueue/queue.go +++ b/outgoingEventQueue/queue.go @@ -10,9 +10,12 @@ Should preferably also be able to tell when a server is just gone and stop bothe Implementation idea: New job gets send to queue (via function call). Queue then launches a new goroutine for that job -Goroutine then launches multiple new goroutines, one per targeted inbox +Goroutine then launches multiple new goroutines, one per targeted inbox, filtered by blocked targets Each of those will wait for a ticker or a stop signal On each ticker they'll try to deliver the payload to the target inbox If a stop signal comes in, push the payload and goal into the db to pick up again later On stop or sucessful delivery, exit the goroutine +Stop can also be used to suspend outbound traffic to another instance to help reduce the load on them + +TODO: Think of ways to make this more configurable for how data is being sent how fast */