Compare commits
No commits in common. "57deaa3670acfe3e1b063747e23a49a19932c00e" and "1d9802530d4b2255a05c7c348a6c2d212e53a672" have entirely different histories.
57deaa3670
...
1d9802530d
|
@ -0,0 +1 @@
|
||||||
|
/target
|
|
@ -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}"
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
44
flabk.go
44
flabk.go
|
@ -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'"
|
|
||||||
// }
|
|
|
@ -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"`
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
35
go.mod
|
@ -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
100
go.sum
|
@ -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=
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
// // Package asld handles JSON-LD for asflab
|
|
||||||
// //
|
|
||||||
// // This will not go well
|
|
||||||
package asld
|
|
|
@ -1 +0,0 @@
|
||||||
package asld
|
|
|
@ -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)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod db;
|
||||||
|
pub mod users;
|
|
@ -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),
|
||||||
|
}
|
|
@ -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(())
|
||||||
|
}
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
mod html;
|
||||||
|
pub mod servek;
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod profiles;
|
|
@ -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 {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
body {
|
||||||
|
background-color: black;
|
||||||
|
color: rebeccapurple;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>flabk - not found</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>not found</h1>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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, "/"))
|
|
||||||
}
|
|
Loading…
Reference in New Issue