sync for backup

This commit is contained in:
Melody Becker 2024-08-22 19:57:53 +02:00
parent 1fbdf7fc9d
commit a9916acea5
No known key found for this signature in database
92 changed files with 35330 additions and 882 deletions

29
.gitignore vendored
View file

@ -3,3 +3,32 @@ db.sqlite
.env .env
.lapce/ .lapce/
*/tmp.* */tmp.*
/frontend/
# --- Section ember frontend ---
# compiled output
/frontend-src/dist/
/frontend-src/declarations/
# dependencies
/frontend-src/node_modules/
# misc
/frontend-src/.env*
/frontend-src/.pnp*
/frontend-src/.eslintcache
/frontend-src/coverage/
/frontend-src/npm-debug.log*
/frontend-src/testem.log
/frontend-src/yarn-error.log
# ember-try
/frontend-src/.node_modules.ember-try/
/frontend-src/npm-shrinkwrap.json.ember-try
/frontend-src/package.json.ember-try
/frontend-src/package-lock.json.ember-try
/frontend-src/yarn.lock.ember-try
# broccoli-debug
/frontend-src/DEBUG/

View file

@ -1,24 +0,0 @@
package ap
import "strings"
// Header used for making requests for AP resources
// Not used yet
const OUTBOUND_REQUEST_HEADER = "application/ld+json, application/json;q=0.9, application/javascript;q=0.5, text/javascript;q=0.5, text/plain;q=0.2, */*;q=0.1"
var contentHeadersToCheck = []string{
"application/json",
"application/ld+json",
}
// Check a given string if it contains any of the content types specified in
// the contentHeadersToCheck slice
// Used for differentiating requests for the ActivityPub version of some data vs frontend version
func ContainsApContentHeader(toCheck string) bool {
for _, h := range contentHeadersToCheck {
if strings.Contains(toCheck, h) {
return true
}
}
return false
}

View file

@ -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
}

View file

@ -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"
)

View file

@ -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",
},
},
}

View file

@ -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"
}
]
}
]

View file

@ -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"
}
]
}
]

View file

@ -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"
}
]
}
]

View file

@ -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"
}
]
}
]

View file

@ -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
}
)

View file

@ -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": <Insert data for that object here>
// This makes it easier to deal with things as they now have a very consistent naming scheme
// See /ap/constants.go for those
parsed, err := processor.Expand(u.String(), opts)
if err != nil {
return nil, fmt.Errorf("failed to process remote document: %w", err)
}
if len(parsed) == 0 {
return nil, fmt.Errorf("document has a length of 0")
}
typed, ok := parsed[0].(ApThing)
if !ok {
return nil, fmt.Errorf("couldn't cast data to ApThing")
}
return typed, nil
}
// Compact an AP object down into a compressed json-ld form
// That compacted form should be accepted by all AP servers
// It also handles context for any fields
// Content should only use keys defined in /ap/constants.go though
// Other things might get lost in translation
func Compact(content map[string]any) ([]byte, error) {
opts := ld.NewJsonLdOptions("")
processor := ld.NewJsonLdProcessor()
res, err := processor.Compact(content, allContext, opts)
if err != nil {
return nil, fmt.Errorf("failed to compact data: %w", err)
}
return json.Marshal(res)
}

View file

@ -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
}

View file

@ -1,26 +1,135 @@
package config package config
import (
"errors"
"fmt"
"os"
"github.com/BurntSushi/toml"
)
type ConfigSSL struct { 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 // 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 // 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 // Mail adress to use in case of using LetsEncrypt
AdminMail *string AdminMail *string `toml:"admin_mail"`
} }
type ConfigGeneral struct { type ConfigGeneral struct {
Domain string // The domain this server operates under Domain string `toml:"domain"` // The domain this server operates under
FullDomain string `toml:"full_domain"` // The full url the server operates under (without port), eg "http://localhost", the public port will be appended as needed
PublicPort int `toml:"public_port"` // The public facing port, usually either 80 or 443
PrivatePort int `toml:"private_port"` // The port the server should launch at
// Explanation:
// The public port is the port to connect to from outside the server to access it.
// The private port is where the server will open for itself on launch
// Important for reverse proxies like nginx or traeffik
// Where you the public port would be either 80 (http) or 443 (https), but the private one could be 3000 for example
} }
type ConfigAdmin struct { type ConfigAdmin struct {
Username string Username string `toml:"username"`
PasswordHash string PasswordHash string `toml:"password_hash"`
}
type ConfigStorage struct {
IsPostgres bool `toml:"is_postgres"`
Uri string `toml:"uri"`
}
type ConfigMail struct {
Host string `toml:"host"`
Port int `toml:"port"`
Username string `toml:"username"`
Password string `toml:"password"`
EncryptionOverwrite *string `toml:"encryption_overwrite,omitempty"`
KeepAliveOverwrite *bool `toml:"keep_alive_overwrite,omitempty"`
ConnectTimeoutSecondsOverwrite *int `toml:"connect_timeout_seconds_overwrite,omitempty"`
SendTimeoutSecondsOverwrite *int `toml:"send_timeout_seconds_overwrite,omitempty"`
TemplateOverwriteDirectory *string `toml:"template_overwrite_directory,omitempty"`
} }
type Config struct { type Config struct {
General ConfigGeneral General ConfigGeneral `toml:"general"`
SSL ConfigSSL SSL ConfigSSL `toml:"ssl"`
Admin ConfigAdmin Admin ConfigAdmin `toml:"admin"`
Storage ConfigStorage `toml:"storage"`
Mail ConfigMail `toml:"mail"`
}
const DEFAULT_CONFIG_FILE_PATH = "config.toml"
// "Global" variable for accessing the config
// If nil, no config has been loaded yet
var Global *Config
var ErrGlobalConfigNotSet = errors.New("global config not set")
// The default config is for a local debug environment
var defaultConfig = Config{
General: ConfigGeneral{
Domain: "localhost",
PublicPort: 8080,
PrivatePort: 8080,
},
SSL: ConfigSSL{
HandleSSL: false,
UseLetsEncrypt: nil,
CertificateFile: nil,
AdminMail: nil,
},
Admin: ConfigAdmin{
Username: "admin",
PasswordHash: "", // No password
},
Storage: ConfigStorage{
IsPostgres: false,
Uri: "db.sqlite",
},
}
func LoadConfigFromFile(file string, tryDefaultPath, saveIntoGlobal bool) (*Config, error) {
conf := &Config{}
data, err := os.ReadFile(file)
if err != nil && !tryDefaultPath {
return nil, fmt.Errorf("failed to read config %s: %w", file, err)
}
if err != nil && tryDefaultPath {
conf, err = loadFromDefaultPath()
if err != nil {
return nil, fmt.Errorf("failed to read custom and default config: %w", err)
}
}
err = toml.Unmarshal(data, &conf)
if err != nil {
return nil, fmt.Errorf("failed to convert from toml: %w", err)
}
if saveIntoGlobal {
Global = conf
}
return conf, nil
}
func loadFromDefaultPath() (*Config, error) {
data, err := os.ReadFile(DEFAULT_CONFIG_FILE_PATH)
if err != nil {
return nil, fmt.Errorf(
"failed to load default config path %s: %w",
DEFAULT_CONFIG_FILE_PATH,
err,
)
}
conf := Config{}
err = toml.Unmarshal(data, &conf)
if err != nil {
return nil, fmt.Errorf(
"failed to parse file content of %s as toml: %w",
DEFAULT_CONFIG_FILE_PATH,
err,
)
}
return &conf, nil
} }

76
example_config.toml Normal file
View file

@ -0,0 +1,76 @@
# General information for the server, primarely domain and port
[general]
# The domain the server operates under
domain = "localhost"
# The full domain to connect to the server, excluding port
full_domain = "http://localhost"
# The port the server is accessed from by the public, usually 80 or 443
public_port = 8080
# The port the server actually operates under
# This is where for example nginx or Traeffik should connect to
private_port = 8080
# How the server should handle SSL (for https)
[ssl]
# Whether the server should handle SSL itself
# Recommended to be false if behind a reverse proxy like nginx or Traeffik
handle_ssl = false
# Required if handle_ssl is true
# Whether the server should use Lets Encrypt for getting the certificate
# use_lets_encrypt = true
# Required if use_lets_encrypt is false
# The certificate file to use for SSL
# certificate_file = some-certificate.pim
# Required if use_lets_encrypt is true
# The admin mail for Lets Encrypt to send certificate infos to
# admin_mail = "admin@example.com"
# Login details for the root admin account
[admin]
username = "admin"
# Empty password hash means no password set
# TODO: Include used hashing algorithm
password_hash = ""
# Where to find the db and what type it is
[storage]
# If the uri points to a postgres db or not (sqlite otherwise)
is_postgres = false
# The uri for the db. A filepath for sqlite, postgres url otherwise
uri = "db.sqlite"
# Details for the mail server to use for sending stuff
# TODO: Extend if server uses multiple accoutns later
[mail]
host = "smtp.example.com"
port = 587
username = "noreply@example.com"
password = "example"
# Overwrite the used encryption method
# Defaults to StartTLS
# TODO: Include all options here
# encryption_overwrite = "StartTLS"
# Overwrite whether the server should keep the connection alive constantly
# Default is false
# keep_alive_overwrite = false
# Overwrite the amount of seconds before the connection times out
# Default is 10
# connection_timeout_seconds_overwrite = 10
# Overwrite the amount of seconds before a mail send times out
# Default is 10
# send_timeout_seconds_overwrite = 10
# Overwrite the templates used for creating mails
# Defaults to built-in one if no matching name for an action is found
# TODO: Include all names for the various actions
# template_directory_overwrite = "templates"

7
featureTargets.md Normal file
View file

@ -0,0 +1,7 @@
- "Lockdown" mode per user or server-wide
- Lockdown means that incoming traffic will be entirely binned
- Optional scanning of notes and media for bad content using Microsoft's services
- Extendable to entire instances by checking their public feed for a bit
- ActivityPub bypass protocol
- AP is slow and really annoying to deal with
- Allows sharing metadata like link previews or scan results

View file

@ -0,0 +1,19 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
[*.hbs]
insert_final_newline = false
[*.{diff,md}]
trim_trailing_whitespace = false

7
frontend-src/.ember-cli Normal file
View file

@ -0,0 +1,7 @@
{
/**
Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
*/
"isTypeScriptProject": true
}

View file

@ -0,0 +1,13 @@
# unconventional js
/blueprints/*/files/
# compiled output
/dist/
# misc
/coverage/
!.*
.*/
# ember-try
/.node_modules.ember-try/

55
frontend-src/.eslintrc.js Normal file
View file

@ -0,0 +1,55 @@
'use strict';
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
},
plugins: ['ember', '@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:ember/recommended',
'plugin:prettier/recommended',
],
env: {
browser: true,
},
rules: {},
overrides: [
// ts files
{
files: ['**/*.ts'],
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {},
},
// node files
{
files: [
'./.eslintrc.js',
'./.prettierrc.js',
'./.stylelintrc.js',
'./.template-lintrc.js',
'./ember-cli-build.js',
'./testem.js',
'./blueprints/*/index.js',
'./config/**/*.js',
'./lib/*/index.js',
'./server/**/*.js',
],
env: {
browser: false,
node: true,
},
extends: ['plugin:n/recommended'],
},
{
// test files
files: ['tests/**/*-test.{js,ts}'],
extends: ['plugin:qunit/recommended'],
},
],
};

47
frontend-src/.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: CI
on:
push:
branches:
- main
- master
pull_request: {}
concurrency:
group: ci-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
lint:
name: "Lint"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
cache: npm
- name: Install Dependencies
run: npm ci
- name: Lint
run: npm run lint
test:
name: "Test"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
cache: npm
- name: Install Dependencies
run: npm ci
- name: Run Tests
run: npm test

25
frontend-src/.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
# compiled output
/dist/
/declarations/
# dependencies
/node_modules/
# misc
/.env*
/.pnp*
/.eslintcache
/coverage/
/npm-debug.log*
/testem.log
/yarn-error.log
# ember-try
/.node_modules.ember-try/
/npm-shrinkwrap.json.ember-try
/package.json.ember-try
/package-lock.json.ember-try
/yarn.lock.ember-try
# broccoli-debug
/DEBUG/

View file

@ -0,0 +1,13 @@
# unconventional js
/blueprints/*/files/
# compiled output
/dist/
# misc
/coverage/
!.*
.*/
# ember-try
/.node_modules.ember-try/

View file

@ -0,0 +1,12 @@
'use strict';
module.exports = {
overrides: [
{
files: '*.{js,ts}',
options: {
singleQuote: true,
},
},
],
};

View file

@ -0,0 +1,8 @@
# unconventional files
/blueprints/*/files/
# compiled output
/dist/
# addons
/.node_modules.ember-try/

View file

@ -0,0 +1,5 @@
'use strict';
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
};

View file

@ -0,0 +1,5 @@
'use strict';
module.exports = {
extends: 'recommended',
};

View file

@ -0,0 +1,3 @@
{
"ignore_dirs": ["dist"]
}

56
frontend-src/README.md Normal file
View file

@ -0,0 +1,56 @@
# frontend-src
This README outlines the details of collaborating on this Ember application.
A short introduction of this app could easily go here.
## Prerequisites
You will need the following things properly installed on your computer.
* [Git](https://git-scm.com/)
* [Node.js](https://nodejs.org/) (with npm)
* [Ember CLI](https://cli.emberjs.com/release/)
* [Google Chrome](https://google.com/chrome/)
## Installation
* `git clone <repository-url>` this repository
* `cd frontend-src`
* `npm install`
## Running / Development
* `npm run start`
* Visit your app at [http://localhost:4200](http://localhost:4200).
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
### Code Generators
Make use of the many generators for code, try `ember help generate` for more details
### Running Tests
* `npm run test`
* `npm run test:ember -- --server`
### Linting
* `npm run lint`
* `npm run lint:fix`
### Building
* `npm exec ember build` (development)
* `npm run build` (production)
### Deploying
Specify what it takes to deploy your app.
## Further Reading / Useful Links
* [ember.js](https://emberjs.com/)
* [ember-cli](https://cli.emberjs.com/release/)
* Development Browser Extensions
* [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
* [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)

12
frontend-src/app/app.ts Normal file
View file

@ -0,0 +1,12 @@
import Application from '@ember/application';
import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from 'frontend-src/config/environment';
export default class App extends Application {
modulePrefix = config.modulePrefix;
podModulePrefix = config.podModulePrefix;
Resolver = Resolver;
}
loadInitializers(App, config.modulePrefix);

View file

View file

@ -0,0 +1 @@
{{yield}}

View file

@ -0,0 +1,14 @@
import Component from '@glimmer/component';
export interface NoteSignature {
// The arguments accepted by the component
Args: {};
// Any blocks yielded by the component
Blocks: {
default: [];
};
// The element to which `...attributes` is applied in the component template
Element: null;
}
export default class NoteComponent extends Component<NoteSignature> {}

View file

@ -0,0 +1,14 @@
/**
* Type declarations for
* import config from 'frontend-src/config/environment'
*/
declare const config: {
environment: string;
modulePrefix: string;
podModulePrefix: string;
locationType: 'history' | 'hash' | 'none';
rootURL: string;
APP: Record<string, unknown>;
};
export default config;

View file

View file

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>FrontendSrc</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for "head"}}
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/vendor.css">
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/frontend-src.css">
{{content-for "head-footer"}}
</head>
<body>
{{content-for "body"}}
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/frontend-src.js"></script>
{{content-for "body-footer"}}
</body>
</html>

View file

View file

@ -0,0 +1,14 @@
import EmberRouter from '@ember/routing/router';
import config from 'frontend-src/config/environment';
export default class Router extends EmberRouter {
location = config.locationType;
rootURL = config.rootURL;
}
Router.map(function () {
this.route('notes', function () {
this.route('note', { path: '/:note_id' });
this.route('index', { path: '/' });
});
});

View file

View file

@ -0,0 +1,3 @@
import Route from '@ember/routing/route';
export default class NotesRoute extends Route {}

View file

@ -0,0 +1,3 @@
import Route from '@ember/routing/route';
export default class NotesIndexRoute extends Route {}

View file

@ -0,0 +1,9 @@
import Route from '@ember/routing/route';
import type Transition from '@ember/routing/transition';
export default class NotesNoteRoute extends Route {
model(params: any, transition: Transition) {
console.log(params);
return { title: params.note_id };
}
}

View file

@ -0,0 +1 @@
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */

View file

@ -0,0 +1,4 @@
{{page-title "FrontendSrc"}}
<h1>Home page</h1>
{{outlet}}

View file

@ -0,0 +1,3 @@
{{page-title "Notes"}}
<h2>Note wrapper</h2>
{{outlet}}

View file

@ -0,0 +1,4 @@
{{page-title "Index"}}
<h3>Note index</h3>
{{outlet}}

View file

@ -0,0 +1,4 @@
{{page-title "Note"}}
<h3>Note {{@model.title}}</h3>
{{outlet}}

View file

@ -0,0 +1,21 @@
{
"schemaVersion": "1.0.0",
"packages": [
{
"name": "ember-cli",
"version": "5.7.0",
"blueprints": [
{
"name": "app",
"outputRepo": "https://github.com/ember-cli/ember-new-output",
"codemodsSource": "ember-app-codemods-manifest@1",
"isBaseBlueprint": true,
"options": [
"--ci-provider=github",
"--typescript"
]
}
]
}
]
}

View file

@ -0,0 +1,48 @@
'use strict';
module.exports = function (environment) {
const ENV = {
modulePrefix: 'frontend-src',
environment,
rootURL: '/',
locationType: 'history',
EmberENV: {
EXTEND_PROTOTYPES: false,
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
},
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
},
};
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
// ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
// ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment === 'test') {
// Testem prefers this...
ENV.locationType = 'none';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
}
if (environment === 'production') {
// here you can enable a production-specific feature
}
return ENV;
};

View file

@ -0,0 +1,7 @@
{
"application-template-wrapper": false,
"default-async-observers": true,
"jquery-integration": false,
"template-only-glimmer-components": true,
"no-implicit-route-model": true
}

View file

@ -0,0 +1,11 @@
'use strict';
const browsers = [
'last 1 Chrome versions',
'last 1 Firefox versions',
'last 1 Safari versions',
];
module.exports = {
browsers,
};

View file

@ -0,0 +1,13 @@
'use strict';
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function (defaults) {
const app = new EmberApp(defaults, {
'ember-cli-babel': { enableTypeScriptTransform: true },
// Add options here
});
return app.toTree();
};

33765
frontend-src/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

113
frontend-src/package.json Normal file
View file

@ -0,0 +1,113 @@
{
"name": "frontend-src",
"version": "0.0.0",
"private": true,
"description": "Small description for frontend-src goes here",
"repository": "",
"license": "MIT",
"author": "",
"directories": {
"doc": "doc",
"test": "tests"
},
"scripts": {
"build": "ember build --environment=production",
"lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"",
"lint:css": "stylelint \"**/*.css\"",
"lint:css:fix": "concurrently \"npm:lint:css -- --fix\"",
"lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"",
"lint:hbs": "ember-template-lint .",
"lint:hbs:fix": "ember-template-lint . --fix",
"lint:js": "eslint . --cache",
"lint:js:fix": "eslint . --fix",
"lint:types": "tsc --noEmit",
"start": "ember serve",
"test": "concurrently \"npm:lint\" \"npm:test:*\" --names \"lint,test:\"",
"test:ember": "ember test"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@ember/optional-features": "^2.1.0",
"@ember/string": "^3.1.1",
"@ember/test-helpers": "^3.3.0",
"@glimmer/component": "^1.1.2",
"@glimmer/tracking": "^1.1.2",
"@glint/environment-ember-loose": "^1.3.0",
"@glint/template": "^1.3.0",
"@tsconfig/ember": "^3.0.4",
"@types/ember": "^4.0.11",
"@types/ember-data": "^4.4.16",
"@types/ember-data__adapter": "^4.0.6",
"@types/ember-data__model": "^4.0.5",
"@types/ember-data__serializer": "^4.0.6",
"@types/ember-data__store": "^4.0.7",
"@types/ember__application": "^4.0.11",
"@types/ember__array": "^4.0.10",
"@types/ember__component": "^4.0.22",
"@types/ember__controller": "^4.0.12",
"@types/ember__debug": "^4.0.8",
"@types/ember__destroyable": "^4.0.5",
"@types/ember__engine": "^4.0.11",
"@types/ember__error": "^4.0.6",
"@types/ember__helper": "^4.0.7",
"@types/ember__modifier": "^4.0.9",
"@types/ember__object": "^4.0.12",
"@types/ember__owner": "^4.0.9",
"@types/ember__polyfills": "^4.0.6",
"@types/ember__routing": "^4.0.22",
"@types/ember__runloop": "^4.0.10",
"@types/ember__service": "^4.0.9",
"@types/ember__string": "^3.16.3",
"@types/ember__template": "^4.0.7",
"@types/ember__test": "^4.0.6",
"@types/ember__utils": "^4.0.7",
"@types/qunit": "^2.19.10",
"@types/rsvp": "^4.0.9",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"broccoli-asset-rev": "^3.0.0",
"concurrently": "^8.2.2",
"ember-auto-import": "^2.7.2",
"ember-cli": "~5.7.0",
"ember-cli-app-version": "^6.0.1",
"ember-cli-babel": "^8.2.0",
"ember-cli-clean-css": "^3.0.0",
"ember-cli-dependency-checker": "^3.3.2",
"ember-cli-htmlbars": "^6.3.0",
"ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-sri": "^2.1.1",
"ember-cli-terser": "^4.0.2",
"ember-data": "~5.3.3",
"ember-fetch": "^8.1.2",
"ember-load-initializers": "^2.1.2",
"ember-modifier": "^4.1.0",
"ember-page-title": "^8.2.2",
"ember-qunit": "^8.0.2",
"ember-resolver": "^11.0.1",
"ember-source": "~5.7.0",
"ember-template-lint": "^5.13.0",
"ember-welcome-page": "^7.0.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-ember": "^11.12.0",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-qunit": "^8.1.1",
"loader.js": "^4.7.0",
"prettier": "^3.2.5",
"qunit": "^2.20.1",
"qunit-dom": "^2.0.0",
"stylelint": "^15.11.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-prettier": "^4.1.0",
"tracked-built-ins": "^3.3.0",
"typescript": "^5.3.3",
"webpack": "^5.90.3"
},
"engines": {
"node": ">= 18"
},
"ember": {
"edition": "octane"
}
}

View file

@ -0,0 +1,3 @@
# http://www.robotstxt.org
User-agent: *
Disallow:

23
frontend-src/testem.js Normal file
View file

@ -0,0 +1,23 @@
'use strict';
module.exports = {
test_page: 'tests/index.html?hidepassed',
disable_watching: true,
launch_in_ci: ['Chrome'],
launch_in_dev: ['Chrome'],
browser_start_timeout: 120,
browser_args: {
Chrome: {
ci: [
// --no-sandbox is needed when running Chrome inside a container
process.env.CI ? '--no-sandbox' : null,
'--headless',
'--disable-dev-shm-usage',
'--disable-software-rasterizer',
'--mute-audio',
'--remote-debugging-port=0',
'--window-size=1440,900',
].filter(Boolean),
},
},
};

View file

@ -0,0 +1,43 @@
import {
setupApplicationTest as upstreamSetupApplicationTest,
setupRenderingTest as upstreamSetupRenderingTest,
setupTest as upstreamSetupTest,
type SetupTestOptions,
} from 'ember-qunit';
// This file exists to provide wrappers around ember-qunit's
// test setup functions. This way, you can easily extend the setup that is
// needed per test type.
function setupApplicationTest(hooks: NestedHooks, options?: SetupTestOptions) {
upstreamSetupApplicationTest(hooks, options);
// Additional setup for application tests can be done here.
//
// For example, if you need an authenticated session for each
// application test, you could do:
//
// hooks.beforeEach(async function () {
// await authenticateSession(); // ember-simple-auth
// });
//
// This is also a good place to call test setup functions coming
// from other addons:
//
// setupIntl(hooks); // ember-intl
// setupMirage(hooks); // ember-cli-mirage
}
function setupRenderingTest(hooks: NestedHooks, options?: SetupTestOptions) {
upstreamSetupRenderingTest(hooks, options);
// Additional setup for rendering tests can be done here.
}
function setupTest(hooks: NestedHooks, options?: SetupTestOptions) {
upstreamSetupTest(hooks, options);
// Additional setup for unit tests can be done here.
}
export { setupApplicationTest, setupRenderingTest, setupTest };

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>FrontendSrc Tests</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for "head"}}
{{content-for "test-head"}}
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
<link rel="stylesheet" href="{{rootURL}}assets/frontend-src.css">
<link rel="stylesheet" href="{{rootURL}}assets/test-support.css">
{{content-for "head-footer"}}
{{content-for "test-head-footer"}}
</head>
<body>
{{content-for "body"}}
{{content-for "test-body"}}
<div id="qunit"></div>
<div id="qunit-fixture">
<div id="ember-testing-container">
<div id="ember-testing"></div>
</div>
</div>
<script src="/testem.js" integrity="" data-embroider-ignore></script>
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/test-support.js"></script>
<script src="{{rootURL}}assets/frontend-src.js"></script>
<script src="{{rootURL}}assets/tests.js"></script>
{{content-for "body-footer"}}
{{content-for "test-body-footer"}}
</body>
</html>

View file

View file

@ -0,0 +1,26 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'frontend-src/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | note', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
await render(hbs`<Note />`);
assert.dom().hasText('');
// Template block usage:
await render(hbs`
<Note>
template block text
</Note>
`);
assert.dom().hasText('template block text');
});
});

View file

@ -0,0 +1,12 @@
import Application from 'frontend-src/app';
import config from 'frontend-src/config/environment';
import * as QUnit from 'qunit';
import { setApplication } from '@ember/test-helpers';
import { setup } from 'qunit-dom';
import { start } from 'ember-qunit';
setApplication(Application.create(config.APP));
setup(QUnit.assert);
start();

View file

View file

@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'frontend-src/tests/helpers';
module('Unit | Route | notes', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
const route = this.owner.lookup('route:notes');
assert.ok(route);
});
});

View file

@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'frontend-src/tests/helpers';
module('Unit | Route | notes/index', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
const route = this.owner.lookup('route:notes/index');
assert.ok(route);
});
});

View file

@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'frontend-src/tests/helpers';
module('Unit | Route | notes/note', function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
const route = this.owner.lookup('route:notes/note');
assert.ok(route);
});
});

View file

@ -0,0 +1,14 @@
{
"extends": "@tsconfig/ember/tsconfig.json",
"compilerOptions": {
// The combination of `baseUrl` with `paths` allows Ember's classic package
// layout, which is not resolvable with the Node resolution algorithm, to
// work with TypeScript.
"baseUrl": ".",
"paths": {
"frontend-src/tests/*": ["tests/*"],
"frontend-src/*": ["app/*"],
"*": ["types/*"]
}
}
}

View file

@ -0,0 +1,7 @@
/**
* Catch-all for ember-data.
*/
export default interface ModelRegistry {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}

1
frontend-src/types/global.d.ts vendored Normal file
View file

@ -0,0 +1 @@
import '@glint/environment-ember-loose';

24
go.mod
View file

@ -1,10 +1,17 @@
module gitlab.com/mstarongitlab/linstrom module gitlab.com/mstarongitlab/linstrom
go 1.22.2 go 1.22.5
require ( require (
github.com/BurntSushi/toml v1.4.0
github.com/glebarez/sqlite v1.11.0
github.com/go-webauthn/webauthn v0.10.2
github.com/google/uuid v1.6.0
github.com/piprate/json-gold v0.5.0 github.com/piprate/json-gold v0.5.0
github.com/sirupsen/logrus v1.9.3 github.com/sethvargo/go-limiter v1.0.0
github.com/xhit/go-simple-mail/v2 v2.16.0
gitlab.com/mstarongitlab/block-things-middleware v0.0.0-20240722113247-31e2984cb9d5
gorm.io/driver/postgres v1.5.7
gorm.io/gorm v1.25.10 gorm.io/gorm v1.25.10
) )
@ -12,26 +19,27 @@ require (
github.com/dustin/go-humanize v1.0.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.6.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect github.com/go-test/deep v1.1.1 // 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.9 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // 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.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-isatty v0.0.17 // 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/mitchellh/mapstructure v1.5.0 // indirect
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
gitlab.com/mstarongitlab/goutils v1.2.0 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.15.0 // indirect
gorm.io/driver/postgres v1.5.7 // indirect
modernc.org/libc v1.22.5 // indirect modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect

42
go.sum
View file

@ -1,3 +1,6 @@
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -9,16 +12,19 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= 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 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4= 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/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 h1:v1oeLmoaa+gPOaZqUdDentu6Rl7HkSSsmOT6gxEQHhE=
github.com/go-webauthn/x v0.1.9/go.mod h1:pJNMlIMP1SU7cN8HNlKJpLEnFHCygLCvaLZ8a1xeoQA= github.com/go-webauthn/x v0.1.9/go.mod h1:pJNMlIMP1SU7cN8HNlKJpLEnFHCygLCvaLZ8a1xeoQA=
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 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= 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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@ -31,12 +37,18 @@ 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/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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 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/mattn/go-isatty v0.0.17/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 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/piprate/json-gold v0.5.0 h1:RmGh1PYboCFcchVFuh2pbSWAZy4XJaqTMU4KQYsApbM= github.com/piprate/json-gold v0.5.0 h1:RmGh1PYboCFcchVFuh2pbSWAZy4XJaqTMU4KQYsApbM=
github.com/piprate/json-gold v0.5.0/go.mod h1:WZ501QQMbZZ+3pXFPhQKzNwS1+jls0oqov3uQ2WasLs= github.com/piprate/json-gold v0.5.0/go.mod h1:WZ501QQMbZZ+3pXFPhQKzNwS1+jls0oqov3uQ2WasLs=
github.com/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
@ -44,26 +56,38 @@ github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prY
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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 h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
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/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4=
github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
gitlab.com/mstarongitlab/block-things-middleware v0.0.0-20240722113247-31e2984cb9d5 h1:BMoO20Z7EEV5QRbxWiun9EoAXPQp6apEbnVgPPV36L0=
gitlab.com/mstarongitlab/block-things-middleware v0.0.0-20240722113247-31e2984cb9d5/go.mod h1:XKgioEQc65Hhx9hd/DbkwbDv0PJv+oYlAOIZt2TCvHw=
gitlab.com/mstarongitlab/goutils v1.2.0 h1:hVpc2VikWkgmX7Gbey9I72eqgmg/6GcKZ4q+M9ZBd0E=
gitlab.com/mstarongitlab/goutils v1.2.0/go.mod h1:SvqfzFxgashuZPqR9kPwQ9gFA7I1yskZjhmGmY2pAow=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 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/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 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/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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

5
mailer/NewUserRequest.go Normal file
View file

@ -0,0 +1,5 @@
package mailer
type NewUserMailData struct {
Domain string
}

12
mailer/mail.go Normal file
View file

@ -0,0 +1,12 @@
package mailer
import (
"io/fs"
mail "github.com/xhit/go-simple-mail/v2"
)
type MailClient struct {
mailServer *mail.SMTPServer
templatesFs fs.FS
}

View file

@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New user request</title>
<link href="{{ .Domain }}/static/css/NewUserRequest.css" rel="stylesheet" />
</head>
<body>
<h1>New account request</h1>
<p>New user is awaiting account approval</p>
<h3>Information</h3>
<ul>
<li>Account name: {{ .Username }}</li>
<li>Account mail: {{ .MailAddress }}</li>
</ul>
<h4>Request reason</h4>
<p>{{ .Reason }}</p>
</body>
</html>

29
server-old/contextVals.go Normal file
View file

@ -0,0 +1,29 @@
package server
import (
"fmt"
"net/http"
"github.com/rs/zerolog"
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
"gitlab.com/mstarongitlab/linstrom/storage"
)
func ContextGetStorage(w http.ResponseWriter, r *http.Request) *storage.Storage {
val, _ := r.Context().Value(middlewares.CONTEXT_KEY_STORAGE).(*storage.Storage)
return val
}
func ContextGetLogger(w http.ResponseWriter, r *http.Request) *zerolog.Logger {
val, _ := r.Context().Value(middlewares.CONTEXT_KEY_LOGRUS).(*zerolog.Logger)
return val
}
func contextFail(w http.ResponseWriter, contextValName string) {
http.Error(
w,
fmt.Sprintf("failed to get %s from request context", contextValName),
http.StatusInternalServerError,
)
}

View file

@ -0,0 +1,112 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/sirupsen/logrus"
"gitlab.com/mstarongitlab/linstrom/config"
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
"gitlab.com/mstarongitlab/linstrom/storage"
)
type webfingerUrl struct {
Relation string `json:"rel"`
Type string `json:"type"`
Url string `json:"href"`
}
// NOTE: Unused for now
// Reason: No endpoint for eg follow authorisation yet
type webfingerTemplate struct {
Relation string `json:"rel"`
Template string `json:"template"`
}
type webfingerResponse struct {
Subject string `json:"subject"`
// Any because it's either a webfingerTemplate or webfingerUrl
Links []any `json:"links"`
}
// Mount under /.well-known/webfinger
// Handles webfinger requests which are used to determine whether an account exists on this server
// Additionally, a sucessful query will return a set of links related to that account, such as the activitypub view and the web view
func webfingerHandler(w http.ResponseWriter, r *http.Request) {
logEntry, ok := r.Context().Value(middlewares.CONTEXT_KEY_LOGRUS).(*logrus.Entry)
if !ok {
http.Error(w, "couldn't get logging entry from context", http.StatusInternalServerError)
return
}
store := storage.Storage{}
requestedResource := r.FormValue("resource")
if requestedResource == "" {
http.Error(w, "bad request. Include \"resource\" parameter", http.StatusBadRequest)
logEntry.Infoln("No resource parameter. Cancelling")
return
}
accName := strings.TrimPrefix(requestedResource, "acc:")
acc, err := store.FindLocalAccount(accName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
logEntry.WithError(err).Warningln("couldn't find account")
return
}
finger, err := accToWebfinger(acc)
if err != nil {
http.Error(w, "failed to build webfinger", http.StatusInternalServerError)
return
}
data, err := json.Marshal(finger)
if err != nil {
http.Error(w, "failed to build json", http.StatusInternalServerError)
return
}
fmt.Fprint(w, string(data))
}
func accToWebfinger(acc *storage.User) (*webfingerResponse, error) {
// First ensure config is set
if config.Global == nil {
return nil, config.ErrGlobalConfigNotSet
}
// Then build the ap link
apLink := webfingerUrl{
Relation: "self",
Type: "application/activity+json",
Url: config.Global.General.FullDomain,
}
if config.Global.General.PublicPort != 80 &&
config.Global.General.PublicPort != 443 {
apLink.Url += fmt.Sprintf(":%d", config.Global.General.PublicPort)
}
apLink.Url += "/api/ap/user/" + acc.ID
// Now the web view
viewLink := webfingerUrl{
Relation: "http://webfinger.net/rel/profile-page",
Type: "text/html",
Url: config.Global.General.FullDomain,
}
if config.Global.General.PublicPort != 80 &&
config.Global.General.PublicPort != 443 {
viewLink.Url += fmt.Sprintf(":%d", config.Global.General.PublicPort)
}
viewLink.Url += "/@" + acc.GetHandleNameOnly()
// TODO: Add follow authorisation template once the endpoint is available
response := webfingerResponse{
Subject: fmt.Sprintf("acct:%s", strings.TrimPrefix(acc.Handle, "@")),
Links: []any{
apLink,
viewLink,
},
}
return &response, nil
}

View file

@ -0,0 +1,57 @@
package server
import (
"net/http"
"gitlab.com/mstarongitlab/linstrom/ap"
)
// Helper func for redirecting a request to another url if that request does not the proper content type headers
func redirectIfNotApRequest(w http.ResponseWriter, r *http.Request, redirectTarget string) bool {
logger := ContextGetLogger(w, r)
if logger == nil {
return false
}
if ap.ContainsApContentHeader(r.Header.Get("Content-Type")) {
return false
}
// redirect with code 307 temporary redirect so that the client sends the same request, but to the given redirect target url instead
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
logger.Info().
Str("from-url", r.URL.RawPath).
Str("to-url", redirectTarget).
Msg("Redirecting non-ap request to ap endpoint to proper endpoint")
return true
}
// Mount under /api/ap/note/{id}
// Handles requests for the AP version of a note with the given id
// And redirects non-ap requests to the web version
func noteApHandler(w http.ResponseWriter, r *http.Request) {
store := ContextGetStorage(w, r)
if store == nil {
return
}
logger := ContextGetLogger(w, r)
if logger == nil {
return
}
// First things first, get the note id from the url
noteId := r.PathValue("id")
// If there is no id (empty string means no value was provided), error out
if noteId == "" {
logger.Info().Msg("Attempted to request a note without providing an ID")
http.Error(w, "missing note id", http.StatusBadRequest)
return
}
if redirectIfNotApRequest(w, r, "/notes/"+noteId) {
return
}
note, err := store.FindNoteById(noteId)
}
// Mount under /api/ap/user/{id}
func userApHandler(w http.ResponseWriter, _ *http.Request) {
}

View file

@ -0,0 +1,4 @@
package server
// Endpoints for the frontend to get its data from
// This is only for Linstrom's own web frontend, not for other options such as Mastodon, *key and plemora

View file

@ -0,0 +1,20 @@
package server
import "net/http"
// Endpoints for the frontend rendering, not data the frontend might use
// Aka serves the static html and assets file needed for rendering the base frontend which will take over from there
// Later on, might also add endpoints for the simple and small frontend
// Mount under /notes/{id}
// Returns webview for notes
// Though maybe it also just returns the general ember export file and ember then renders the neeeded data
func notesWebFrontendHandler(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "not implemented yet", http.StatusInternalServerError)
}
// Mount under /@{id}
// Returns webview for accounts
func accountWebFrontendHandler(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "not implemented yet", http.StatusInternalServerError)
}

View file

@ -0,0 +1,18 @@
package middlewares
import (
"context"
"net/http"
)
func InjectContextValuesBuilder(values map[ContextKey]any) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
for k, v := range values {
ctx = context.WithValue(ctx, k, v)
}
h.ServeHTTP(w, r.WithContext(ctx))
})
}
}

View file

@ -4,17 +4,17 @@ import (
"context" "context"
"net/http" "net/http"
"github.com/sirupsen/logrus" "github.com/rs/zerolog/log"
) )
const CONTEXT_KEY_LOGRUS = ContextKey("logrus") const CONTEXT_KEY_LOGRUS = ContextKey("logrus")
// Inject a logrus entry into the context that has the url path already set // Inject a logrus entry into the context that has the url path already set
func InjectLogrusMiddleware(next http.Handler) http.Handler { func InjectLoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqContext := r.Context() reqContext := r.Context()
entry := logrus.WithField("url-path", r.URL.Path) logger := log.With().Ctx(r.Context()).Str("url-path", r.URL.RawPath).Logger()
newContext := context.WithValue(reqContext, CONTEXT_KEY_LOGRUS, entry) newContext := context.WithValue(reqContext, CONTEXT_KEY_LOGRUS, &logger)
next.ServeHTTP(w, r.WithContext(newContext)) next.ServeHTTP(w, r.WithContext(newContext))
}) })
} }

View file

@ -0,0 +1,43 @@
package middlewares
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/sethvargo/go-limiter"
)
type RateLimiter struct {
store limiter.Store
next http.Handler
}
func (rl *RateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// What to not rate limit
// Includes various localhost and loopback interfaces
// TODO: Only allow requests with a valid unlimit token in the "rate-limit-bypass" field bypass rate limit
if r.FormValue("rate-limit-bypass") != "" ||
strings.Contains(r.RemoteAddr, "127.0.0.1") ||
strings.Contains(r.RemoteAddr, "localhost") ||
strings.Contains(r.RemoteAddr, "::1") {
rl.next.ServeHTTP(w, r)
return
}
tokens, remaining, resetTime, ok, err := rl.store.Take(r.Context(), r.RemoteAddr)
if err != nil {
http.Error(w, "rate limiter problem", http.StatusInternalServerError)
return
}
w.Header().Add("X-RateLimit-Limit", fmt.Sprint(tokens))
w.Header().Add("X-RateLimit-Remaining", fmt.Sprint(remaining))
w.Header().Add("X-RateLimit-Reset", fmt.Sprint(resetTime))
if ok {
rl.next.ServeHTTP(w, r)
} else {
t := time.Unix(0, int64(resetTime)).UTC().Format(time.RFC1123)
w.Header().Add("Retry-After", t)
http.Error(w, "rate limited", http.StatusTooManyRequests)
}
}

43
server-old/server.go Normal file
View file

@ -0,0 +1,43 @@
package server
import (
"net/http"
"regexp"
blockthingsmiddleware "gitlab.com/mstarongitlab/block-things-middleware"
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
"gitlab.com/mstarongitlab/linstrom/storage"
)
type Server struct {
handler http.Handler
}
func NewServer(store *storage.Storage) *Server {
handler := http.NewServeMux()
handler.HandleFunc("/.well-known/webfinger", webfingerHandler)
// handler.Handle("/api/", http.StripPrefix("/ap", buildApiRouter()))
handler.HandleFunc("/notes/{id}", notesWebFrontendHandler)
handler.HandleFunc("/@{id}", accountWebFrontendHandler)
withMiddlewares := middlewares.ChainMiddlewares(
handler,
middlewares.InjectLoggerMiddleware,
middlewares.InjectStorageMiddlewareBuilder(store),
middlewares.InjectContextValuesBuilder(map[middlewares.ContextKey]any{
middlewares.CONTEXT_KEY_STORAGE: store,
}),
blockthingsmiddleware.BuildMiddleware(blockthingsmiddleware.Config{
UserAgentRegexes: []*regexp.Regexp{
regexp.MustCompile("ArchiveBot"),
regexp.MustCompile("GPTBot"),
},
CheckDomain: false,
}),
)
return &Server{
handler: withMiddlewares,
}
}

View file

@ -1,36 +0,0 @@
package server
import (
"fmt"
"net/http"
"strings"
"github.com/sirupsen/logrus"
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
"gitlab.com/mstarongitlab/linstrom/storage"
)
// Mount under /.well-known/webfinger
func webfingerHandler(w http.ResponseWriter, r *http.Request) {
logEntry, ok := r.Context().Value(middlewares.CONTEXT_KEY_LOGRUS).(*logrus.Entry)
if !ok {
http.Error(w, "couldn't get logging entry from context", http.StatusInternalServerError)
return
}
store := storage.Storage{}
requestedResource := r.FormValue("resource")
if requestedResource == "" {
http.Error(w, "bad request. Include \"resource\" parameter", http.StatusBadRequest)
logEntry.Infoln("No resource parameter. Cancelling")
return
}
accName := strings.TrimPrefix(requestedResource, "acc:")
acc, err := store.FindLocalAccount(accName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
logEntry.WithError(err).Warningln("couldn't find account")
return
}
fmt.Fprint(w, acc)
}

View file

@ -1,23 +0,0 @@
package server
import (
"net/http"
"gitlab.com/mstarongitlab/linstrom/ap"
)
// Mount at /notes/{note-id}
// Handles the note endpoint
// Serves the json-ld representation of a note OR the frontend view
func noteHandler(w http.ResponseWriter, r *http.Request) {
if ap.ContainsApContentHeader(r.Header.Get("Content-Type")) {
apNote(w, r)
} else {
renderNote(w, r)
}
}
func renderNote(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not implemented yet", http.StatusInternalServerError)
}
func apNote(w http.ResponseWriter, r *http.Request) {}

View file

@ -1,27 +0,0 @@
package server
import (
"net/http"
"gitlab.com/mstarongitlab/linstrom/server/middlewares"
"gitlab.com/mstarongitlab/linstrom/storage"
)
type Server struct {
handler http.Handler
}
func NewServer(store *storage.Storage) *Server {
handler := http.NewServeMux()
handler.HandleFunc("/.well-known/webfinger", webfingerHandler)
// handler.Handle("/api/", http.StripPrefix("/ap", buildApiRouter()))
withMiddlewares := middlewares.ChainMiddlewares(
handler,
middlewares.InjectLogrusMiddleware,
middlewares.InjectStorageMiddlewareBuilder(store),
)
return &Server{
handler: withMiddlewares,
}
}

View file

@ -21,6 +21,7 @@ type MediaFile struct {
// And caching a file for those few times would be a waste of storage // 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 // Caching user and server icons locally however should reduce burden on remote servers by quite a bit though
LocallyCached bool LocallyCached bool
Sensitive bool // Whether the media is marked as sensitive. If so, hide it in the UI by default
} }
// Placeholder media file. Acts as placeholder for media file fields that have not been initialised yet but need a value // Placeholder media file. Acts as placeholder for media file fields that have not been initialised yet but need a value

View file

@ -42,3 +42,13 @@ var placeholderNote = &Note{
OriginServer: "placeholder", OriginServer: "placeholder",
Tags: []string{}, Tags: []string{},
} }
// Try and find a note with a given ID
func (store *Storage) FindNoteById(id string) (*Note, error) {
note := Note{}
res := store.db.First(&note, id)
if res.Error != nil {
return nil, res.Error
}
return &note, nil
}

View file

@ -1,6 +1,7 @@
package storage package storage
import ( import (
"errors"
"fmt" "fmt"
"github.com/glebarez/sqlite" "github.com/glebarez/sqlite"
@ -14,6 +15,8 @@ type Storage struct {
db *gorm.DB db *gorm.DB
} }
var ErrAccountNotFound = errors.New("account not found")
// Build a new storage using sqlite as database backend // Build a new storage using sqlite as database backend
func NewStorageSqlite(filePath string) (*Storage, error) { func NewStorageSqlite(filePath string) (*Storage, error) {
db, err := gorm.Open(sqlite.Open(filePath)) db, err := gorm.Open(sqlite.Open(filePath))
@ -34,12 +37,14 @@ func NewStoragePostgres(dbUrl string) (*Storage, error) {
func storageFromEmptyDb(db *gorm.DB) (*Storage, error) { func storageFromEmptyDb(db *gorm.DB) (*Storage, error) {
// AutoMigrate ensures the db is in a state where all the structs given here // 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 // have their own tables and relations setup. It also updates tables if necessary
db.AutoMigrate( if err := db.AutoMigrate(
placeholderMediaFile, placeholderMediaFile,
placeholderUser(), placeholderUser,
placeholderNote, placeholderNote,
placeholderServer, placeholderServer,
) ); err != nil {
return nil, fmt.Errorf("problem while auto migrating: %w", err)
}
// Afterwards add the placeholder entries for each table. // Afterwards add the placeholder entries for each table.
// FirstOrCreate either creates a new entry or retrieves the first matching one // 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 // We only care about the creation if there is none yet, so no need to carry the result over
@ -62,7 +67,14 @@ func storageFromEmptyDb(db *gorm.DB) (*Storage, error) {
}, nil }, nil
} }
// TODO: Placeholder. Update to proper implementation later. Including signature func (s *Storage) FindLocalAccount(handle string) (*User, error) {
func (s *Storage) FindLocalAccount(handle string) (string, error) { acc := User{}
return handle, nil res := s.db.Where("handle = ?", handle).First(&acc)
if res.Error != nil {
return nil, fmt.Errorf("failed to query db: %w", res.Error)
}
if res.RowsAffected == 0 {
return nil, ErrAccountNotFound
}
return nil, errors.New("unimplemented")
} }

View file

@ -1,6 +1,9 @@
package storage package storage
import ( import (
"errors"
"fmt"
"strings"
"time" "time"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
@ -55,6 +58,35 @@ type User struct {
// Access should be rare enough anyway // Access should be rare enough anyway
Passkeys map[string]webauthn.Credential `gorm:"serializer:json"` 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
// Restrictions applied to the account
// Flag value, can be multiple
Restrictions AccountRestriction
}
var placeholderUser = &User{
ID: "placeholder",
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,
} }
func NewEmptyUser() *User { func NewEmptyUser() *User {
@ -85,8 +117,32 @@ func NewEmptyUser() *User {
} }
} }
func placeholderUser() *User { // Get a stored user by the ID the user has been stored with
tmp := NewEmptyUser() // Either returns a valid user and nil for the error
tmp.ID = "placeholder" // Or nil for the user and an error
return tmp func (s *Storage) GetUserByID(id string) (*User, error) {
user := User{}
res := s.db.First(&user, "id = ?", id)
// Check if any error except NotFound occured
// If so, wrap it a little
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("problem while getting user from db: %w", res.Error)
}
// Then check if an error occured and said error is NotFound
// If it is, just pass it forward
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
return nil, res.Error
}
return &user, nil
}
// Get only the name part of the handle a user has
func (u *User) GetHandleNameOnly() string {
// First remove the leading @ (TrimPrefix) to achieve a format of username@server
// Then split at the @ to the server and user seperately
// And return the first element since that is the username
// Note: Getting the first element will always be safe
// since trim returns a string guaranteed (empty is ok)
// and if Split doesn't do anything (eg empty string) it just returns the input in the first elemen it just returns the input in the first element
return strings.Split(strings.TrimPrefix(u.Handle, "@"), "@")[0]
} }

View file

@ -0,0 +1,34 @@
package storage
type AccountRestriction int64
const (
// Account has no restrictions applied
ACCOUNT_RESTRICTION_NONE = AccountRestriction(0)
// All messages of the account get a content warning applied if none is set
// Warning could be something like "Message auto-CWd by server"
ACCOUNT_RESTRICTION_AUTO_CW = AccountRestriction(1 << iota)
// Disable accessing the account via login or access token
ACCOUNT_RESTRICTION_DISABLE_LOGIN
// Disable sending activities to other servers if the account is local
// Or reject all activities if the account is remote
ACCOUNT_RESTRICTION_NO_FEDERATION
// Disallow sending direct messages from that account
ACCOUNT_RESTRICTION_NO_DMS
// Disallow sending follower only messages from that account
ACCOUNT_RESTRICTION_NO_FOLLOWER_POSTS
// Disable outbound follow requests (restricted account can't send follow requests)
ACCOUNT_RESTRICTION_DISABLE_OUTBOUND_FOLLOWS
// Disable inbound follow requests (all follow requests to that account are automatically rejected)
ACCOUNT_RESTRICTION_DISABLE_INBOUND_FOLLOWS
// Forces all posts by that account to be follower only
ACCOUNT_RESTRICTION_FORCE_FOLLOWERS_ONLY
// Disable all outbound activities of an account.
// Includes sending, updating or deleting own notes
// as well as boosting or reacting to any notes
ACCOUNT_RESTRICTION_DISABLE_ACTIVITIES
// Account can only be viewed while logged in or via verified requests to the AP endpoints
ACCOUNT_RESTRICTIONS_NO_PUBLIC_ACCESS
// Force tag all media the account posts as sensitive
ACCOUNT_RESTRICTIONS_FORCE_MEDIA_SENSITIVE
)