Compare commits
No commits in common. "1d9802530d4b2255a05c7c348a6c2d212e53a672" and "57deaa3670acfe3e1b063747e23a49a19932c00e" have entirely different histories.
1d9802530d
...
57deaa3670
|
@ -1 +0,0 @@
|
||||||
/target
|
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
// 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}"
|
|
||||||
// }
|
|
||||||
]
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
|
@ -1,20 +0,0 @@
|
||||||
[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"
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
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'"
|
||||||
|
// }
|
|
@ -0,0 +1,36 @@
|
||||||
|
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"`
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
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
|
||||||
|
)
|
|
@ -0,0 +1,100 @@
|
||||||
|
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=
|
|
@ -1,10 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
// // Package asld handles JSON-LD for asflab
|
||||||
|
// //
|
||||||
|
// // This will not go well
|
||||||
|
package asld
|
|
@ -0,0 +1 @@
|
||||||
|
package asld
|
|
@ -0,0 +1,239 @@
|
||||||
|
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)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -1,47 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod db;
|
|
||||||
pub mod users;
|
|
|
@ -1,95 +0,0 @@
|
||||||
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
48
src/main.rs
|
@ -1,48 +0,0 @@
|
||||||
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
12
src/model.rs
|
@ -1,12 +0,0 @@
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
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)
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
mod html;
|
|
||||||
pub mod servek;
|
|
|
@ -1,116 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
pub mod profiles;
|
|
|
@ -1,115 +0,0 @@
|
||||||
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 {}
|
|
|
@ -1,8 +0,0 @@
|
||||||
body {
|
|
||||||
background-color: black;
|
|
||||||
color: rebeccapurple;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>flabk - not found</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>not found</h1>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>flabk</title>
|
|
||||||
<link rel="stylesheet" href="/static/style/main.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>hi</h1>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<!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>
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
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, "/"))
|
||||||
|
}
|
Loading…
Reference in New Issue