sync for backup
This commit is contained in:
parent
1fbdf7fc9d
commit
a9916acea5
92 changed files with 35330 additions and 882 deletions
29
.gitignore
vendored
29
.gitignore
vendored
|
@ -3,3 +3,32 @@ db.sqlite
|
|||
.env
|
||||
.lapce/
|
||||
*/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/
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
)
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
)
|
59
ap/parser.go
59
ap/parser.go
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
129
config/config.go
129
config/config.go
|
@ -1,26 +1,135 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
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
|
||||
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 {
|
||||
Username string
|
||||
PasswordHash string
|
||||
Username string `toml:"username"`
|
||||
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 {
|
||||
General ConfigGeneral
|
||||
SSL ConfigSSL
|
||||
Admin ConfigAdmin
|
||||
General ConfigGeneral `toml:"general"`
|
||||
SSL ConfigSSL `toml:"ssl"`
|
||||
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
76
example_config.toml
Normal 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
7
featureTargets.md
Normal 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
|
19
frontend-src/.editorconfig
Normal file
19
frontend-src/.editorconfig
Normal 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
7
frontend-src/.ember-cli
Normal 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
|
||||
}
|
13
frontend-src/.eslintignore
Normal file
13
frontend-src/.eslintignore
Normal 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
55
frontend-src/.eslintrc.js
Normal 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
47
frontend-src/.github/workflows/ci.yml
vendored
Normal 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
25
frontend-src/.gitignore
vendored
Normal 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/
|
13
frontend-src/.prettierignore
Normal file
13
frontend-src/.prettierignore
Normal file
|
@ -0,0 +1,13 @@
|
|||
# unconventional js
|
||||
/blueprints/*/files/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
|
||||
# misc
|
||||
/coverage/
|
||||
!.*
|
||||
.*/
|
||||
|
||||
# ember-try
|
||||
/.node_modules.ember-try/
|
12
frontend-src/.prettierrc.js
Normal file
12
frontend-src/.prettierrc.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
overrides: [
|
||||
{
|
||||
files: '*.{js,ts}',
|
||||
options: {
|
||||
singleQuote: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
8
frontend-src/.stylelintignore
Normal file
8
frontend-src/.stylelintignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
# unconventional files
|
||||
/blueprints/*/files/
|
||||
|
||||
# compiled output
|
||||
/dist/
|
||||
|
||||
# addons
|
||||
/.node_modules.ember-try/
|
5
frontend-src/.stylelintrc.js
Normal file
5
frontend-src/.stylelintrc.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
|
||||
};
|
5
frontend-src/.template-lintrc.js
Normal file
5
frontend-src/.template-lintrc.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: 'recommended',
|
||||
};
|
3
frontend-src/.watchmanconfig
Normal file
3
frontend-src/.watchmanconfig
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"ignore_dirs": ["dist"]
|
||||
}
|
56
frontend-src/README.md
Normal file
56
frontend-src/README.md
Normal 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
12
frontend-src/app/app.ts
Normal 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);
|
0
frontend-src/app/components/.gitkeep
Normal file
0
frontend-src/app/components/.gitkeep
Normal file
1
frontend-src/app/components/note.hbs
Normal file
1
frontend-src/app/components/note.hbs
Normal file
|
@ -0,0 +1 @@
|
|||
{{yield}}
|
14
frontend-src/app/components/note.ts
Normal file
14
frontend-src/app/components/note.ts
Normal 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> {}
|
14
frontend-src/app/config/environment.d.ts
vendored
Normal file
14
frontend-src/app/config/environment.d.ts
vendored
Normal 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;
|
0
frontend-src/app/controllers/.gitkeep
Normal file
0
frontend-src/app/controllers/.gitkeep
Normal file
0
frontend-src/app/helpers/.gitkeep
Normal file
0
frontend-src/app/helpers/.gitkeep
Normal file
24
frontend-src/app/index.html
Normal file
24
frontend-src/app/index.html
Normal 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>
|
0
frontend-src/app/models/.gitkeep
Normal file
0
frontend-src/app/models/.gitkeep
Normal file
14
frontend-src/app/router.ts
Normal file
14
frontend-src/app/router.ts
Normal 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: '/' });
|
||||
});
|
||||
});
|
0
frontend-src/app/routes/.gitkeep
Normal file
0
frontend-src/app/routes/.gitkeep
Normal file
3
frontend-src/app/routes/notes.ts
Normal file
3
frontend-src/app/routes/notes.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class NotesRoute extends Route {}
|
3
frontend-src/app/routes/notes/index.ts
Normal file
3
frontend-src/app/routes/notes/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Route from '@ember/routing/route';
|
||||
|
||||
export default class NotesIndexRoute extends Route {}
|
9
frontend-src/app/routes/notes/note.ts
Normal file
9
frontend-src/app/routes/notes/note.ts
Normal 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 };
|
||||
}
|
||||
}
|
1
frontend-src/app/styles/app.css
Normal file
1
frontend-src/app/styles/app.css
Normal file
|
@ -0,0 +1 @@
|
|||
/* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */
|
4
frontend-src/app/templates/application.hbs
Normal file
4
frontend-src/app/templates/application.hbs
Normal file
|
@ -0,0 +1,4 @@
|
|||
{{page-title "FrontendSrc"}}
|
||||
|
||||
<h1>Home page</h1>
|
||||
{{outlet}}
|
3
frontend-src/app/templates/notes.hbs
Normal file
3
frontend-src/app/templates/notes.hbs
Normal file
|
@ -0,0 +1,3 @@
|
|||
{{page-title "Notes"}}
|
||||
<h2>Note wrapper</h2>
|
||||
{{outlet}}
|
4
frontend-src/app/templates/notes/index.hbs
Normal file
4
frontend-src/app/templates/notes/index.hbs
Normal file
|
@ -0,0 +1,4 @@
|
|||
{{page-title "Index"}}
|
||||
|
||||
<h3>Note index</h3>
|
||||
{{outlet}}
|
4
frontend-src/app/templates/notes/note.hbs
Normal file
4
frontend-src/app/templates/notes/note.hbs
Normal file
|
@ -0,0 +1,4 @@
|
|||
{{page-title "Note"}}
|
||||
|
||||
<h3>Note {{@model.title}}</h3>
|
||||
{{outlet}}
|
21
frontend-src/config/ember-cli-update.json
Normal file
21
frontend-src/config/ember-cli-update.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
48
frontend-src/config/environment.js
Normal file
48
frontend-src/config/environment.js
Normal 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;
|
||||
};
|
7
frontend-src/config/optional-features.json
Normal file
7
frontend-src/config/optional-features.json
Normal 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
|
||||
}
|
11
frontend-src/config/targets.js
Normal file
11
frontend-src/config/targets.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
const browsers = [
|
||||
'last 1 Chrome versions',
|
||||
'last 1 Firefox versions',
|
||||
'last 1 Safari versions',
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
browsers,
|
||||
};
|
13
frontend-src/ember-cli-build.js
Normal file
13
frontend-src/ember-cli-build.js
Normal 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
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
113
frontend-src/package.json
Normal 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"
|
||||
}
|
||||
}
|
3
frontend-src/public/robots.txt
Normal file
3
frontend-src/public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
# http://www.robotstxt.org
|
||||
User-agent: *
|
||||
Disallow:
|
23
frontend-src/testem.js
Normal file
23
frontend-src/testem.js
Normal 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),
|
||||
},
|
||||
},
|
||||
};
|
43
frontend-src/tests/helpers/index.ts
Normal file
43
frontend-src/tests/helpers/index.ts
Normal 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 };
|
39
frontend-src/tests/index.html
Normal file
39
frontend-src/tests/index.html
Normal 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>
|
0
frontend-src/tests/integration/.gitkeep
Normal file
0
frontend-src/tests/integration/.gitkeep
Normal file
26
frontend-src/tests/integration/components/note-test.ts
Normal file
26
frontend-src/tests/integration/components/note-test.ts
Normal 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');
|
||||
});
|
||||
});
|
12
frontend-src/tests/test-helper.ts
Normal file
12
frontend-src/tests/test-helper.ts
Normal 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();
|
0
frontend-src/tests/unit/.gitkeep
Normal file
0
frontend-src/tests/unit/.gitkeep
Normal file
11
frontend-src/tests/unit/routes/notes-test.ts
Normal file
11
frontend-src/tests/unit/routes/notes-test.ts
Normal 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);
|
||||
});
|
||||
});
|
11
frontend-src/tests/unit/routes/notes/index-test.ts
Normal file
11
frontend-src/tests/unit/routes/notes/index-test.ts
Normal 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);
|
||||
});
|
||||
});
|
11
frontend-src/tests/unit/routes/notes/note-test.ts
Normal file
11
frontend-src/tests/unit/routes/notes/note-test.ts
Normal 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);
|
||||
});
|
||||
});
|
14
frontend-src/tsconfig.json
Normal file
14
frontend-src/tsconfig.json
Normal 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/*"]
|
||||
}
|
||||
}
|
||||
}
|
7
frontend-src/types/ember-data/types/registries/model.d.ts
vendored
Normal file
7
frontend-src/types/ember-data/types/registries/model.d.ts
vendored
Normal 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
1
frontend-src/types/global.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import '@glint/environment-ember-loose';
|
24
go.mod
24
go.mod
|
@ -1,10 +1,17 @@
|
|||
module gitlab.com/mstarongitlab/linstrom
|
||||
|
||||
go 1.22.2
|
||||
go 1.22.5
|
||||
|
||||
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/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
|
||||
)
|
||||
|
||||
|
@ -12,26 +19,27 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/sqlite v1.11.0 // indirect
|
||||
github.com/go-webauthn/webauthn v0.10.2 // indirect
|
||||
github.com/go-test/deep v1.1.1 // indirect
|
||||
github.com/go-webauthn/x v0.1.9 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/google/go-tpm v0.9.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/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/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
|
||||
gitlab.com/mstarongitlab/goutils v1.2.0 // 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
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
|
|
42
go.sum
42
go.sum
|
@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
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/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/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/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,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/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
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/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/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=
|
||||
|
@ -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-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/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/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/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=
|
||||
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
|
||||
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/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.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/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/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
5
mailer/NewUserRequest.go
Normal file
5
mailer/NewUserRequest.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package mailer
|
||||
|
||||
type NewUserMailData struct {
|
||||
Domain string
|
||||
}
|
12
mailer/mail.go
Normal file
12
mailer/mail.go
Normal 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
|
||||
}
|
23
mailer/templates/NewUserRequest.html
Normal file
23
mailer/templates/NewUserRequest.html
Normal 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
29
server-old/contextVals.go
Normal 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,
|
||||
)
|
||||
}
|
112
server-old/endpoint_webfinger.go
Normal file
112
server-old/endpoint_webfinger.go
Normal 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
|
||||
}
|
57
server-old/endpoints_ap.go
Normal file
57
server-old/endpoints_ap.go
Normal 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) {
|
||||
|
||||
}
|
4
server-old/endpoints_web_api.go
Normal file
4
server-old/endpoints_web_api.go
Normal 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
|
20
server-old/endpoints_web_fe.go
Normal file
20
server-old/endpoints_web_fe.go
Normal 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)
|
||||
}
|
18
server-old/middlewares/injectContextValues.go
Normal file
18
server-old/middlewares/injectContextValues.go
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -4,17 +4,17 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
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 {
|
||||
func InjectLoggerMiddleware(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)
|
||||
logger := log.With().Ctx(r.Context()).Str("url-path", r.URL.RawPath).Logger()
|
||||
newContext := context.WithValue(reqContext, CONTEXT_KEY_LOGRUS, &logger)
|
||||
next.ServeHTTP(w, r.WithContext(newContext))
|
||||
})
|
||||
}
|
43
server-old/middlewares/rateLimit.go
Normal file
43
server-old/middlewares/rateLimit.go
Normal 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
43
server-old/server.go
Normal 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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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) {}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ type MediaFile struct {
|
|||
// And caching a file for those few times would be a waste of storage
|
||||
// Caching user and server icons locally however should reduce burden on remote servers by quite a bit though
|
||||
LocallyCached bool
|
||||
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
|
||||
|
|
|
@ -42,3 +42,13 @@ var placeholderNote = &Note{
|
|||
OriginServer: "placeholder",
|
||||
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(¬e, id)
|
||||
if res.Error != nil {
|
||||
return nil, res.Error
|
||||
}
|
||||
return ¬e, nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
|
@ -14,6 +15,8 @@ type Storage struct {
|
|||
db *gorm.DB
|
||||
}
|
||||
|
||||
var ErrAccountNotFound = errors.New("account not found")
|
||||
|
||||
// Build a new storage using sqlite as database backend
|
||||
func NewStorageSqlite(filePath string) (*Storage, error) {
|
||||
db, err := gorm.Open(sqlite.Open(filePath))
|
||||
|
@ -34,12 +37,14 @@ func NewStoragePostgres(dbUrl string) (*Storage, error) {
|
|||
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(
|
||||
if err := db.AutoMigrate(
|
||||
placeholderMediaFile,
|
||||
placeholderUser(),
|
||||
placeholderUser,
|
||||
placeholderNote,
|
||||
placeholderServer,
|
||||
)
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("problem while auto migrating: %w", err)
|
||||
}
|
||||
// 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
|
||||
|
@ -62,7 +67,14 @@ func storageFromEmptyDb(db *gorm.DB) (*Storage, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Placeholder. Update to proper implementation later. Including signature
|
||||
func (s *Storage) FindLocalAccount(handle string) (string, error) {
|
||||
return handle, nil
|
||||
func (s *Storage) FindLocalAccount(handle string) (*User, error) {
|
||||
acc := User{}
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
|
@ -55,6 +58,35 @@ type User struct {
|
|||
// 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
|
||||
// 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 {
|
||||
|
@ -85,8 +117,32 @@ func NewEmptyUser() *User {
|
|||
}
|
||||
}
|
||||
|
||||
func placeholderUser() *User {
|
||||
tmp := NewEmptyUser()
|
||||
tmp.ID = "placeholder"
|
||||
return tmp
|
||||
// Get a stored user by the ID the user has been stored with
|
||||
// Either returns a valid user and nil for the error
|
||||
// Or nil for the user and an error
|
||||
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]
|
||||
}
|
||||
|
|
34
storage/userRestrictionType.go
Normal file
34
storage/userRestrictionType.go
Normal 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
|
||||
)
|
Loading…
Reference in a new issue