Compare commits

...

No commits in common. "57deaa3670acfe3e1b063747e23a49a19932c00e" and "1d9802530d4b2255a05c7c348a6c2d212e53a672" have entirely different histories.

33 changed files with 2221 additions and 971 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

45
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,45 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'flabk'",
"cargo": {
"args": [
"build",
"--bin=flabk",
"--package=flabk"
],
"filter": {
"name": "flabk",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
// {
// "type": "lldb",
// "request": "launch",
// "name": "Debug unit tests in executable 'flabk'",
// "cargo": {
// "args": [
// "test",
// "--no-run",
// "--bin=flabk",
// "--package=flabk"
// ],
// "filter": {
// "name": "flabk",
// "kind": "bin"
// }
// },
// "args": [],
// "cwd": "${workspaceFolder}"
// }
]
}

1570
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "flabk"
version = "0.0.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.64"
base-62 = "0.1.1"
handlebars = "4.3.3"
mime_guess = "2.0.4"
rand = "0.8.5"
rust-embed = "6.4.0"
serde = { version = "1.0.144", features = ["derive", "std", "serde_derive"]}
serde_json = "1.0.85"
tokio = { version = "1", features = ["full"] }
tokio-postgres = { version = "0.7.7", features = ["with-serde_json-1"] }
warp = { version = "0.3.2" }
warp-embed = "0.4.0"

View File

@ -1,44 +0,0 @@
package main
// MUST (client and server) application/ld+json; profile="https://www.w3.org/ns/activitystreams
// SHOULD application/activity+json
// public actor https://www.w3.org/ns/activitystreams#Public
func main() {
panic(asfhttp.New("my.site").Run())
}
// {
// "@context": ["https://www.w3.org/ns/activitystreams",
// {"@language": "en"}],
// "type": "Like",
// "actor": "https://dustycloud.org/chris/",
// "name": "Chris liked 'Minimal ActivityPub update client'",
// "object": "https://rhiaro.co.uk/2016/05/minimal-activitypub",
// "to": ["https://rhiaro.co.uk/#amy",
// "https://dustycloud.org/followers",
// "https://rhiaro.co.uk/followers/"],
// "cc": "https://e14n.com/evan"
// }
// {
// "@context": [
// "https://www.w3.org/ns/activitystreams",
// {
// "@language": "en"
// }
// ],
// "type": "Like",
// "to": [
// "https://rhiaro.co.uk/#amy",
// "https://dustycloud.org/followers",
// "https://rhiaro.co.uk/followers/"
// ],
// "cc": [
// "https://e14n.com/evan"
// ],
// "object": "https://rhiaro.co.uk/2016/05/minimal-activitypub",
// "actor": "https://dustycloud.org/chris/",
// "name": "Chris liked 'Minimal ActivityPub update client'"
// }

View File

@ -1,36 +0,0 @@
package ap
import (
"github.com/go-ap/jsonld"
)
type Object struct {
Context jsonld.Context `jsonld:"@context,omitempty,collapsible"`
Type string `jsonld:"type"`
Type2 string `jsonld:"@type"`
ID jsonld.IRI `jsonld:"id,omitempty"`
Name string `jsonld:"name,omitempty"`
Source *Source `jsonld:"source,omitempty"`
To []Object `jsonld:"to,omitempty,collapsible"`
CC []Object `jsonld:"cc,omitempty,collapsible"`
Object *Object `jsonld:"object,omitempty,collapsible"`
Actor *Actor `jsonld:"actor,omitempty,collapsible"`
}
func (o Object) Collapse() interface{} {
return o.ID
}
type Actor struct {
ID jsonld.IRI `jsonld:"id,omitempty"`
Name string `jsonld:"name,omitempty"`
}
func (a Actor) Collapse() interface{} {
return a.ID
}
type Source struct {
Content string `jsonld:"content,omitempty"`
MediaType string `jsonld:"mediaType,omitempty"`
}

View File

@ -1,52 +0,0 @@
package ap_test
import (
"fmt"
"testing"
"github.com/go-ap/jsonld"
"github.com/stretchr/testify/require"
"sectorinf.com/emilis/flabk/flabk/ap"
)
func GetExample() ap.Object {
return ap.Object{
Context: jsonld.Context{
{
IRI: jsonld.IRI("https://www.w3.org/ns/activitystreams"),
},
{
Term: jsonld.LanguageKw,
IRI: "en",
},
},
Type: "Like",
Actor: &ap.Actor{
ID: "https://dustycloud.org/chris/",
},
Name: "Chris liked 'Minimal ActivityPub update client'",
Object: &ap.Object{
ID: "https://rhiaro.co.uk/2016/05/minimal-activitypub",
},
To: []ap.Object{
{ID: "https://rhiaro.co.uk/#amy"},
{ID: "https://dustycloud.org/followers"},
{ID: "https://rhiaro.co.uk/followers/"},
},
CC: []ap.Object{
{ID: "https://e14n.com/evan"},
},
}
}
func TestObject(t *testing.T) {
that := require.New(t)
result, err := jsonld.Marshal(GetExample())
that.NoError(err)
fmt.Println(string(result))
fmt.Println()
another := ap.Object{}
err = jsonld.Unmarshal(result, &another)
that.NoError(err)
fmt.Printf("%+v\n", another)
}

View File

@ -1,46 +0,0 @@
package flabweb
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"sectorinf.com/emilis/flabk/util/uriutil"
)
const (
EndpointOutbox = "outbox"
)
var (
tempOutboxIDCauseImNotStoringThis = 0
)
type Server struct {
router *gin.Engine
v1 *gin.RouterGroup
hostname string
}
func New(hostname string) Server {
router := gin.Default()
v1 := router.Group("v1")
server := Server{
hostname: hostname,
router: router,
v1: v1,
}
v1.POST(EndpointOutbox, server.Outbox)
return server
}
func (s Server) Run() error {
return s.router.Run(":8081")
}
func (s Server) Outbox(ctx *gin.Context) {
id := tempOutboxIDCauseImNotStoringThis
tempOutboxIDCauseImNotStoringThis++
ctx.Header("Location", uriutil.JoinURIs(s.hostname, EndpointOutbox, strconv.Itoa(id)))
ctx.Status(http.StatusCreated)
}

35
go.mod
View File

@ -1,35 +0,0 @@
module sectorinf.com/emilis/flabk
go 1.18
require (
github.com/gin-gonic/gin v1.8.1
github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d
github.com/piprate/json-gold v0.4.1
github.com/stretchr/testify v1.7.2
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/goccy/go-json v0.9.8 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

100
go.sum
View File

@ -1,100 +0,0 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d h1:Z/oRXMlZHjvjIqDma1FrIGL3iE5YL7MUI0bwYEZ6qbA=
github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE=
github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
github.com/piprate/json-gold v0.4.1 h1:JYbYN36n6YcAYipKy3ttv3X2HDQPeqWqmwta35NPj04=
github.com/piprate/json-gold v0.4.1/go.mod h1:OK1z7UgtBZk06n2cDE2OSq1kffmjFFp5/2yhLLCz9UM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,10 @@
CREATE TABLE users (
id CHAR(22) NOT NULL PRIMARY KEY,
username TEXT NOT NULL,
host TEXT,
display_name TEXT
);
CREATE UNIQUE INDEX u_username_host ON users (username, host);
CREATE UNIQUE INDEX u_username_local ON users (username) WHERE host IS NULL;

View File

@ -1,69 +0,0 @@
// Package coll provides generic collection types and functions
package coll
func Map[T, V any](c Vector[T], f func(T) V) Vector[V] {
out := make([]V, len(c))
for index := 0; index < len(c); index++ {
out[index] = f(c[index])
}
return From(out)
}
func Filter[T any](v Vector[T], f func(T) bool) Vector[T] {
out := WithCap[T](len(v))
for _, t := range v {
if f(t) {
out = out.Push(t)
}
}
return out
}
func Take[T any](v Vector[T], howMany int) Vector[T] {
if len(v) == 0 {
return New[T]()
}
if len(v) < howMany {
howMany = len(v)
}
return From(v[:howMany-1])
}
func Any[T any](v Vector[T], f func(T) bool) bool {
for _, t := range v {
if f(t) {
return true
}
}
return false
}
type numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64
}
func Min[T numeric](v Vector[T]) T {
var min T
if len(v) == 0 {
return min
}
min = v[0]
for _, t := range v {
if min > t {
min = t
}
}
return min
}
func Max[T numeric](v Vector[T]) T {
var max T
for _, t := range v {
if max < t {
max = t
}
}
return max
}

View File

@ -1,138 +0,0 @@
package coll
import "fmt"
const (
DefaultSize = 4
)
type Vector[T any] []T
func New[T any]() Vector[T] {
return WithCap[T](DefaultSize)
}
func WithCap[T any](capacity int) Vector[T] {
return make(Vector[T], 0, capacity)
}
func From[T any](v []T) Vector[T] {
return Vector[T](v)
}
func (v Vector[T]) Push(t T) Vector[T] {
if len(v) == cap(v) {
v = v.upsize()
}
v = v[:len(v)+1]
v[len(v)-1] = t
return v
}
func (v Vector[T]) Append(t ...T) Vector[T] {
if cap(v)-len(v) < len(t) {
v = v.upsizeSpecific(len(t))
}
startLen := len(v)
v = v[:len(v)+len(t)]
var tIndex int
for index := startLen; index < cap(v); index++ {
v[index] = t[tIndex]
tIndex++
}
return v
}
func (v Vector[T]) Remove(index int) Vector[T] {
return From(append(v[:index], v[index+1:]...))
}
// Faster remove function that can be used on vectors where ordering doesn't matter
func (v Vector[T]) RemoveUnordered(index int) Vector[T] {
v[index] = v[len(v)-1]
return v[:len(v)-1]
}
func (v Vector[T]) upsize() Vector[T] {
return v.upsizeSpecific(cap(v))
}
func (v Vector[T]) upsizeSpecific(extraSize int) Vector[T] {
resized := make([]T, len(v), cap(v)+extraSize)
copy(resized, v)
return resized
}
func (v Vector[T]) panicIndex(index int) {
min := -1
if len(v) != 0 {
min = 0
}
panic(fmt.Sprintf("index %d out of range [%d;%d)", index, min, len(v)))
}
// GetSoft tries to get the requested index, or returns a default value if
// it's out of range
func (v Vector[T]) GetSoft(index int) T {
var result T
if index < 0 || index >= len(v) {
return result
}
return v[index]
}
func (v Vector[T]) Get(index int) T {
if index < 0 || index >= len(v) {
v.panicIndex(index)
}
return v[index]
}
func (v Vector[T]) Set(index int, value T) Vector[T] {
if index < 0 || index >= len(v) {
v.panicIndex(index)
}
v[index] = value
return v
}
func (v Vector[T]) Pop() (T, Vector[T]) {
t := v[len(v)-1]
return t, v[:len(v)-1]
}
func (v Vector[T]) Sub(start, end int) Vector[T] {
if start >= len(v) {
v.panicIndex(start)
}
if end >= len(v) {
v.panicIndex(end)
}
if start < 0 {
return From(v[:end])
}
if end < 0 {
return From(v[start:])
}
return From(v[start:end])
}
func (v Vector[T]) Filter(f func(T) bool) Vector[T] {
return Filter(v, f)
}
func (v Vector[T]) Any(f func(T) bool) bool {
return Any(v, f)
}
func (v Vector[T]) Take(howMany int) Vector[T] {
return Take(v, howMany)
}
func (v Vector[T]) Clone() Vector[T] {
clone := make([]T, len(v))
copy(clone, v)
v = clone
return v
}

View File

@ -1,160 +0,0 @@
package coll_test
import (
"testing"
"github.com/stretchr/testify/require"
"sectorinf.com/emilis/flabk/pkg/coll"
)
func TestVector(t *testing.T) {
tests := map[string]func(that *require.Assertions){
"push": func(that *require.Assertions) {
v := coll.New[int]()
v = v.Push(0).Push(1).Push(2).Push(3).Push(4).Push(5)
that.Len(v, 6)
that.EqualValues([]int{0, 1, 2, 3, 4, 5}, v)
},
"len": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
that.Len(v, len(v))
},
"from": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
that.Len(v, 3)
},
"pop": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
t, v := v.Pop()
that.Equal(3, t)
that.EqualValues([]int{1, 2}, v)
},
"withcap": func(that *require.Assertions) {
v := coll.WithCap[int](3)
that.Zero(len(v))
that.Len(v, 0)
},
"remove": func(that *require.Assertions) {
v := coll.New[int]().Push(0).Push(1).Push(2)
r1 := v.Remove(1)
that.Equal(2, len(r1))
that.EqualValues([]int{0, 2}, r1)
that.Panics(func() {
v.Remove(-1)
})
that.Panics(func() {
v.Remove(100000)
})
},
"remove unordered": func(that *require.Assertions) {
v := coll.New[int]().Push(0).Push(1).Push(2)
v = v.RemoveUnordered(1)
that.Equal(2, len(v))
vSlice := v
if vSlice[0] == 0 {
that.Equal(2, vSlice[1])
} else {
that.Equal(2, vSlice[0])
that.Zero(vSlice[1])
}
},
"append": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3}).Append(4, 5, 6)
that.Equal(6, len(v))
that.EqualValues([]int{1, 2, 3, 4, 5, 6}, v)
},
"getsoft": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
that.Equal(2, v.GetSoft(1))
that.Zero(v.GetSoft(-1000))
that.Zero(v.GetSoft(1000))
},
"get": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
that.Equal(2, v.Get(1))
that.Panics(func() {
v.Get(-1000)
})
that.Panics(func() {
v.Get(1000)
})
},
"clone": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
v1 := v.Clone().Set(1, 1)
that.Equal(3, len(v1))
that.EqualValues([]int{1, 1, 3}, v1)
that.EqualValues([]int{1, 2, 3}, v)
},
"set": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3})
v1 := v.Clone().Set(1, 1)
that.Equal(3, len(v1))
that.EqualValues([]int{1, 1, 3}, v1)
v2 := v.Clone().Set(2, 1)
that.Equal(3, len(v2))
that.EqualValues([]int{1, 2, 1}, v2)
v3 := v.Clone().Set(0, 16)
that.Equal(3, len(v3))
that.EqualValues([]int{16, 2, 3}, v3)
that.Panics(func() {
v.Set(-1000, 1)
})
that.Panics(func() {
v.Set(3, 1)
})
},
"sub": func(that *require.Assertions) {
v := coll.From([]int{0, 1, 2, 3, 4, 5, 6})
v1 := v.Clone().Sub(2, 4)
that.Equal(2, len(v1))
that.EqualValues([]int{2, 3}, v1)
v2 := v.Clone().Sub(2, 5)
that.Equal(3, len(v2))
that.EqualValues([]int{2, 3, 4}, v2)
v3 := v.Clone().Sub(5, -1)
that.Equal(2, len(v3))
that.EqualValues([]int{5, 6}, v3)
v4 := v.Clone().Sub(-1, 2)
that.Equal(2, len(v4))
that.EqualValues([]int{0, 1}, v4)
that.Panics(func() {
v.Sub(0, 1000)
})
that.Panics(func() {
v.Sub(4000, 4002)
})
that.Panics(func() {
v.Sub(2, 1)
})
},
"filter": func(that *require.Assertions) {
v := coll.From([]int{1, 2, 3, 4, 5, 6})
v = v.Filter(func(i int) bool { return i%2 == 0 })
that.Len(v, 3)
},
"any": func(that *require.Assertions) {
that.True(coll.From([]int{1, 2, 3, 4, 5, 6}).Any(func(i int) bool { return i == 3 }))
that.False(coll.From([]int{1, 2, 3, 4, 5, 6}).Any(func(i int) bool { return i == 666 }))
},
"take": func(that *require.Assertions) {
that.EqualValues([]int{1, 2, 3}, coll.From([]int{1, 2, 3, 4, 5, 6}).Take(3))
},
}
for name, test := range tests {
t.Run(name, func(tt *testing.T) {
test(require.New(tt))
})
}
}

View File

@ -1,34 +0,0 @@
package epk
import "github.com/stretchr/testify/require"
type ErrorTest func(*require.Assertions, error)
var (
NoError ErrorTest = func(a *require.Assertions, err error) {
a.NoError(err)
}
Error ErrorTest = func(a *require.Assertions, err error) {
a.Error(err)
}
)
func ErrorIs(target error, contains ...string) ErrorTest {
return func(a *require.Assertions, err error) {
a.Error(err)
a.ErrorIs(err, target)
for _, elem := range contains {
a.Contains(err.Error(), elem)
}
}
}
func NotErrorIs(target error, contains ...string) ErrorTest {
return func(a *require.Assertions, err error) {
a.Error(err)
a.NotErrorIs(err, target)
for _, elem := range contains {
a.Contains(err.Error(), elem)
}
}
}

View File

@ -1,4 +0,0 @@
// // Package asld handles JSON-LD for asflab
// //
// // This will not go well
package asld

View File

@ -1 +0,0 @@
package asld

View File

@ -1,239 +0,0 @@
package asld_test
// import (
// "encoding/json"
// "fmt"
// "math"
// "testing"
// "time"
// "sectorinf.com/emilis/flabk/pkg/asld"
// "sectorinf.com/emilis/flabk/pkg/epk"
// "github.com/stretchr/testify/require"
// )
// type testObj struct {
// String string `asld:"string" json:"string"`
// NoTag string
// Int int
// Int8 int8
// Int16 int16
// Int32 int32
// Int64 int64
// Uint uint
// Uint8 uint8
// Uint16 uint16
// Uint32 uint32
// Uint64 uint64
// Float32 float32
// Float64 float64
// // Complex64 complex64
// // Complex128 complex128
// IntPtr *int
// Int8Ptr *int8
// Int16Ptr *int16
// Int32Ptr *int32
// Int64Ptr *int64
// UintPtr *uint
// Uint8Ptr *uint8
// Uint16Ptr *uint16
// Uint32Ptr *uint32
// Uint64Ptr *uint64
// Float32Ptr *float32
// Float64Ptr *float64
// // Complex64Ptr *complex64
// // Complex128Ptr *complex128
// TestPtr *testObj
// TestArray []testObj
// }
// func TestUnmarshal(t *testing.T) {
// tests := map[string]struct {
// obj testObj
// errCheck epk.ErrorTest
// }{
// "string children": {
// obj: testObj{
// String: "hello",
// NoTag: "no_tag",
// Int: math.MaxInt,
// Int8: math.MaxInt8,
// Int16: math.MaxInt16,
// Int32: math.MaxInt32,
// Int64: math.MaxInt64,
// Uint: math.MaxUint,
// Uint8: math.MaxUint8,
// Uint16: math.MaxUint16,
// Uint32: math.MaxUint32,
// Uint64: math.MaxUint64,
// Float32: math.MaxFloat32,
// Float64: math.MaxFloat64,
// TestPtr: &testObj{
// String: "hello2",
// },
// TestArray: []testObj{
// {
// String: "hello3",
// },
// {
// String: "hello4",
// TestPtr: &testObj{
// TestArray: []testObj{
// {
// String: "hello5",
// },
// },
// },
// },
// },
// // Complex64: complex(math.MaxFloat32, math.MaxFloat32),
// // Complex128: complex(math.MaxFloat64, math.MaxFloat64),
// },
// },
// }
// for name, test := range tests {
// test := test
// t.Run(name, func(tt *testing.T) {
// that := require.New(tt)
// objJSON, err := json.MarshalIndent(test.obj, "", " ")
// that.NoError(err)
// result, err := asld.Unmarshal[testObj](objJSON)
// that.NoError(err)
// that.Equal(test.obj, result)
// })
// }
// }
// func TestBench(t *testing.T) {
// obj := testObj{
// String: "hello",
// NoTag: "no_tag",
// Int: math.MaxInt,
// Int8: math.MaxInt8,
// Int16: math.MaxInt16,
// Int32: math.MaxInt32,
// Int64: math.MaxInt64,
// Uint: math.MaxUint,
// Uint8: math.MaxUint8,
// Uint16: math.MaxUint16,
// Uint32: math.MaxUint32,
// Uint64: math.MaxUint64,
// Float32: math.MaxFloat32,
// Float64: math.MaxFloat64,
// // Complex64: complex(math.MaxFloat32, math.MaxFloat32),
// // Complex128: complex(math.MaxFloat64, math.MaxFloat64),
// }
// that := require.New(t)
// asldTotal := int64(0)
// jsonTotal := int64(0)
// jsonMax := int64(0)
// jsonMin := math.MaxInt64
// asldMax := int64(0)
// asldMin := math.MaxInt64
// count := int64(1 << 20)
// for index := int64(0); index < count; index++ {
// objJSON, err := json.Marshal(obj)
// that.NoError(err)
// asldStart := time.Now()
// _, err = asld.Unmarshal[testObj](objJSON)
// asldDur := time.Since(asldStart)
// asldTotal += int64(asldDur)
// if asldDur < time.Duration(asldMin) {
// asldMin = int(asldDur)
// }
// if asldDur > time.Duration(asldMax) {
// asldMax = int64(asldDur)
// }
// that.NoError(err)
// a := testObj{}
// jsonStart := time.Now()
// err = json.Unmarshal(objJSON, &a)
// jsonDur := time.Since(jsonStart)
// jsonTotal += int64(jsonDur)
// if jsonDur < time.Duration(jsonMin) {
// jsonMin = int(jsonDur)
// }
// if jsonDur > time.Duration(jsonMax) {
// jsonMax = int64(jsonDur)
// }
// that.NoError(err)
// }
// fmt.Println(count, "runs")
// fmt.Printf("json avg (%s), min (%s), max (%s)\n", time.Duration(jsonTotal/count), time.Duration(jsonMin), time.Duration(jsonMax))
// fmt.Printf("asld avg (%s), min (%s), max (%s)\n", time.Duration(asldTotal/count), time.Duration(asldMin), time.Duration(asldMax))
// }
// func jsonElemList[T any](that *require.Assertions, a any) [][]byte {
// that.NotNil(a)
// v, ok := a.([]T)
// that.True(ok)
// fields := make([][]byte, len(v))
// for index, field := range v {
// jsonField, err := json.Marshal(field)
// that.NoError(err)
// fields[index] = jsonField
// }
// return fields
// }
// type hello struct {
// Hello string
// }
// func TestArrayMembers(t *testing.T) {
// tests := map[string]struct {
// object any
// errCheck epk.ErrorTest
// toJsonElementList func(*require.Assertions, any) [][]byte
// }{
// "strings": {
// object: []string{"a", "b", "c", "d"},
// toJsonElementList: jsonElemList[string],
// },
// "ints": {
// object: []int{1, 2, 3, 4, 5, 6},
// toJsonElementList: jsonElemList[int],
// },
// "floats": {
// object: []float64{123.456, 789.0123, 456.789},
// toJsonElementList: jsonElemList[float64],
// },
// "strings with commas": {
// object: []string{"this, is what I do", "what, don't like it?"},
// toJsonElementList: jsonElemList[string],
// },
// "objects": {
// object: []hello{{"world"}, {"mom, dad"}},
// toJsonElementList: jsonElemList[hello],
// },
// }
// for name, test := range tests {
// test := test
// t.Run(name, func(tt *testing.T) {
// that := require.New(tt)
// objJson, err := json.Marshal(test.object)
// that.NoError(err)
// w := newWalker(objJson)
// members, err := arrayMembers(w)
// if test.errCheck == nil {
// test.errCheck = epk.NoError
// }
// test.errCheck(that, err)
// expected := test.toJsonElementList(that, test.object)
// that.Len(members, len(expected))
// for index, elem := range expected {
// equivMember := members[index]
// that.Equal(elem, equivMember.content)
// }
// })
// }
// }

47
src/database/db.rs Normal file
View File

@ -0,0 +1,47 @@
use std::sync::Arc;
use super::users::Users;
use tokio::sync::Mutex;
use tokio_postgres::{tls::NoTlsStream, Client, Connection, NoTls, Socket};
const DBERR_UNIQUE: &str = "23505";
#[derive(Clone)]
pub struct DB {
client: Arc<Mutex<Client>>,
}
impl DB {
pub async fn new(host: String, user: String, database: String) -> Result<Self, anyhow::Error> {
let (cl, conn) = tokio_postgres::connect(
format!("host={host} user={user} dbname={database}").as_str(),
NoTls,
)
.await?;
tokio::spawn(async move {
if let Err(e) = conn.await {
eprintln!("connection error: {}", e);
}
});
let client = Arc::new(Mutex::new(cl));
Ok(Self { client })
}
pub fn users(&self) -> Users {
Users::new(self.client.clone())
}
}
pub enum DBError {
Duplicate,
Other(tokio_postgres::Error),
}
impl From<tokio_postgres::Error> for DBError {
fn from(err: tokio_postgres::Error) -> Self {
if let Some(code) = err.code() && code.code() == DBERR_UNIQUE {
return DBError::Duplicate;
}
DBError::Other(err)
}
}

2
src/database/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod db;
pub mod users;

95
src/database/users.rs Normal file
View File

@ -0,0 +1,95 @@
use std::sync::Arc;
use rand::Rng;
use tokio::sync::Mutex;
use tokio_postgres::{Client, Row};
use super::db;
#[derive(Clone)]
pub struct Users(Arc<Mutex<Client>>);
impl Users {
pub fn new(client: Arc<Mutex<Client>>) -> Self {
Self(client)
}
fn new_id() -> String {
let bytes = rand::thread_rng().gen::<[u8; 16]>();
base_62::encode(&bytes)
}
pub async fn create_user(&self, u: User) -> Result<User, db::DBError> {
let row = self.0.lock().await.query_one(
"insert into users (id, username, host, display_name) values ($1, $2, $3, $4) returning id",
&[&Self::new_id(), &u.username, &u.host, &u.display_name],
).await?;
Ok(User {
id: row.get("id"),
username: u.username,
host: u.host,
display_name: u.display_name,
})
}
pub async fn user(&self, by: UserSelect) -> Result<Option<User>, anyhow::Error> {
let where_param: String;
let where_clause = match by {
UserSelect::ID(id) => {
where_param = id;
"id = $1"
}
UserSelect::Username(username) => {
where_param = username;
"username = $1"
}
UserSelect::FullUsername(full) => {
where_param = full;
"(username || '@' || host) = $1"
}
};
let rows = self
.0
.lock()
.await
.query(
format!(
"select id, username, host, display_name from users where {}",
where_clause
)
.as_str(),
&[&where_param],
)
.await?;
if let Some(row) = rows.first() && rows.len() == 1 {
Ok(Some(User::from(row)))
} else {
Ok(None)
}
}
}
pub struct User {
pub id: String,
pub username: String,
pub host: Option<String>,
pub display_name: Option<String>,
}
impl From<&Row> for User {
fn from(row: &Row) -> Self {
Self {
id: row.get("id"),
username: row.get("username"),
host: row.get("host"),
display_name: row.get("display_name"),
}
}
}
pub enum UserSelect {
ID(String),
Username(String),
FullUsername(String),
}

48
src/main.rs Normal file
View File

@ -0,0 +1,48 @@
mod database;
mod model;
mod servek;
mod svc;
use database::db::DB;
use serde::{Deserialize, Serialize};
use servek::servek::Server;
use svc::profiles::Profiler;
#[derive(Clone, Copy, Serialize, Deserialize)]
pub enum ActivityKind {
Create,
Like,
Note,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ActivityLD {
pub context_uri: String,
pub kind: ActivityKind,
pub actor_uri: String,
pub to_uris: Vec<String>,
pub object: Option<ObjectLD>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ObjectLD {
pub context_uri: String,
pub id: Option<String>,
pub kind: Option<ActivityKind>,
pub attributed_to: Option<String>,
pub published: Option<String>,
pub content: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let db = DB::new(
"localhost".to_owned(),
"flabk".to_owned(),
"flabk".to_owned(),
)
.await?;
let profiler = Profiler::new(db.users());
Server::new(profiler).listen_and_serve(8008).await;
Ok(())
}

12
src/model.rs Normal file
View File

@ -0,0 +1,12 @@
use serde::Serialize;
#[derive(Clone, Serialize)]
pub struct Error<'a> {
pub error: &'a str,
}
impl<'a> Error<'a> {
pub fn error(error: &'a str) -> Self {
Self { error }
}
}

91
src/servek/html.rs Normal file
View File

@ -0,0 +1,91 @@
use std::str::FromStr;
use warp::{http::HeaderValue, hyper::Uri, path::Tail, reply::Response, Filter, Rejection, Reply};
use super::servek::{Server, ServerError};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "static"]
struct StaticData;
impl Server {
pub(super) async fn html(
&self,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
Self::index()
.or(self.profile().await)
.or(self.create_profile().await.or(Server::static_files()))
}
fn index() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::get().and(warp::path::end().map(move || {
warp::reply::html(include_str!("../../templates/html/index.html").to_owned())
}))
}
fn with_server(
srv: Server,
) -> impl Filter<Extract = (Server,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || srv.clone())
}
async fn profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::get().and(
warp::path!("@" / String)
.and(Self::with_server(self.clone()))
.and_then(|username: String, srv: Server| async move {
srv.hb
.render(
"profile",
&serde_json::json!(srv
.profiler
.profile(username)
.await
.map_err(|e| ServerError::from(e))?),
)
.map(|html| warp::reply::html(html))
.map_err(|e| ServerError::from(e).reject_self())
}),
)
}
async fn create_profile(&self) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::post().and(
warp::path!("@" / String)
.and(Self::with_server(self.clone()))
.and_then(|username: String, srv: Server| async move {
let user = srv
.profiler
.create_user(username, None)
.await
.map_err(|e| ServerError::from(e));
match user {
Ok(u) => Ok(warp::redirect(
Uri::from_str(format!("/@/{}", u.username).as_str()).unwrap(),
)),
Err(e) => Err(e.reject_self()),
}
}),
)
}
fn static_files() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::get().and(warp::path("static").and(warp::path::tail()).and_then(
|path: Tail| async move {
let asset = match StaticData::get(path.as_str()) {
Some(a) => a,
None => return Err(ServerError::NotFound.reject_self()),
};
let mime = mime_guess::from_path(path.as_str()).first_or_octet_stream();
let mut res = Response::new(asset.data.into());
res.headers_mut().insert(
"Content-Type",
HeaderValue::from_str(mime.as_ref()).unwrap(),
);
Ok(res)
},
))
}
}

2
src/servek/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod html;
pub mod servek;

116
src/servek/servek.rs Normal file
View File

@ -0,0 +1,116 @@
use core::panic;
use std::{convert::Infallible, fmt::Display};
use handlebars::{Handlebars, RenderError};
use warp::{
hyper::StatusCode,
reject::{MethodNotAllowed, Reject},
Filter, Rejection, Reply,
};
use crate::{
database::users::User,
model,
svc::profiles::{Profiler, UserError},
};
#[derive(Clone)]
pub struct Server {
pub(super) hb: Handlebars<'static>,
pub(super) profiler: Profiler,
}
impl Server {
pub fn new(profiler: Profiler) -> Self {
let mut hb = Handlebars::new();
hb.register_template_string("profile", include_str!("../../templates/html/profile.html"))
.expect("profile template");
Self { hb, profiler }
}
pub async fn listen_and_serve(self, port: u16) -> ! {
println!("starting server on port {}", port);
warp::serve(self.html().await.recover(Self::handle_rejection))
.run(([127, 0, 0, 1], port))
.await;
panic!("server stopped prematurely")
}
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
let code;
let message;
if err.is_not_found() {
code = StatusCode::NOT_FOUND;
message = "not found";
} else if let Some(err) = err.find::<ServerError>() {
match err {
ServerError::Internal(err) => {
println!("internal server error: {}", err);
code = StatusCode::INTERNAL_SERVER_ERROR;
message = "internal server error";
}
ServerError::NotFound => {
code = StatusCode::NOT_FOUND;
message = "not found";
}
ServerError::Duplicate => {
code = StatusCode::BAD_REQUEST;
message = "duplicate entry exists";
}
}
} else if let Some(err) = err.find::<MethodNotAllowed>() {
println!("MethodNotAllowed: {:#?}", err);
code = StatusCode::NOT_FOUND;
message = "not found";
} else {
// We should have expected this... Just log and say its a 500
println!("FIXME: unhandled rejection: {:?}", err);
code = StatusCode::INTERNAL_SERVER_ERROR;
message = "internal server error"
}
Ok(warp::reply::with_status(
warp::reply::json(&model::Error::error(message)),
code,
))
}
}
#[derive(Clone, Debug)]
pub(super) enum ServerError {
Internal(String),
NotFound,
Duplicate,
}
impl ServerError {
pub(super) fn reject_self(self) -> Rejection {
warp::reject::custom(self)
}
}
impl Reject for ServerError {}
impl From<RenderError> for ServerError {
fn from(r: RenderError) -> Self {
Self::Internal(r.to_string())
}
}
impl From<UserError> for ServerError {
fn from(u: UserError) -> Self {
match u {
UserError::Duplicate => Self::Duplicate,
UserError::NotFound => Self::NotFound,
UserError::Other(o) => Self::Internal(format!("UserError: {}", o)),
}
}
}
impl Display for ServerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}

1
src/svc/mod.rs Normal file
View File

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

115
src/svc/profiles.rs Normal file
View File

@ -0,0 +1,115 @@
use serde::Serialize;
use warp::{reject::Reject, Rejection};
use crate::database::{
db,
users::{self, UserSelect, Users},
};
#[derive(Clone)]
pub struct Profiler {
db: Users,
}
impl Profiler {
pub fn new(db: Users) -> Self {
Self { db }
}
pub async fn profile(&self, username: String) -> Result<User, UserError> {
let select = if username.contains("@") {
UserSelect::FullUsername(username)
} else {
UserSelect::Username(username)
};
match self.db.user(select).await? {
Some(user) => Ok(User::from(user)),
None => Err(UserError::NotFound),
}
}
pub async fn create_user(
&self,
username: String,
display_name: Option<String>,
) -> Result<User, UserError> {
let result = self
.db
.create_user(
User {
id: String::new(),
username,
display_name,
host: None,
}
.into(),
)
.await?;
Ok(User::from(result))
}
}
#[derive(Clone, Debug, Serialize)]
pub struct User {
pub id: String,
pub username: String,
pub display_name: Option<String>,
pub host: Option<String>,
}
impl From<users::User> for User {
fn from(u: users::User) -> Self {
Self {
id: u.id,
username: u.username,
display_name: u.display_name,
host: u.host,
}
}
}
impl Into<users::User> for User {
fn into(self) -> users::User {
users::User {
id: self.id,
username: self.username,
display_name: self.display_name,
host: self.host,
}
}
}
#[derive(Debug, Clone)]
pub enum UserError {
Duplicate,
NotFound,
Other(String),
}
impl From<anyhow::Error> for UserError {
fn from(err: anyhow::Error) -> Self {
Self::Other(format!("UserError: {}", err))
}
}
impl From<db::DBError> for UserError {
fn from(err: db::DBError) -> Self {
match err {
db::DBError::Duplicate => Self::Duplicate,
db::DBError::Other(e) => Self::Other(e.to_string()),
}
}
}
impl ToString for UserError {
fn to_string(&self) -> String {
match self {
Self::Duplicate => String::from("duplicate insert"),
Self::NotFound => String::from("not found"),
Self::Other(err) => err.clone(),
}
}
}
impl Reject for UserError {}

8
static/style/main.css Normal file
View File

@ -0,0 +1,8 @@
body {
background-color: black;
color: rebeccapurple;
}
h1 {
text-align: center;
}

12
templates/html/404.html Normal file
View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>flabk - not found</title>
</head>
<body>
<h1>not found</h1>
</body>
</html>

13
templates/html/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>flabk</title>
<link rel="stylesheet" href="/static/style/main.css">
</head>
<body>
<h1>hi</h1>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>@{{username}}</title>
<link rel="stylesheet" href="/static/style/main.css">
</head>
<body>
<h1>hi {{username}}, your id is {{id}}</h1>
</body>
</html>

View File

@ -1,13 +0,0 @@
package uriutil
import (
"fmt"
"strings"
)
func JoinURIs(base string, parts ...string) string {
if len(base) < 8 || len(base) > 8 && base[:8] != "https://" {
base = "https://" + base
}
return fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.Join(parts, "/"))
}