225 lines
6.5 KiB
Go
225 lines
6.5 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
webutils "git.mstar.dev/mstar/goutils/http"
|
|
"git.mstar.dev/mstar/goutils/other"
|
|
"github.com/rs/zerolog/hlog"
|
|
"gorm.io/gorm"
|
|
|
|
"git.mstar.dev/mstar/linstrom/config"
|
|
"git.mstar.dev/mstar/linstrom/shared"
|
|
"git.mstar.dev/mstar/linstrom/storage-new"
|
|
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
|
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
|
)
|
|
|
|
var webfingerResourceRegex = regexp.MustCompile(`acct:(?P<username>[\w\.-]+)@(?<domain>[\w\.-]+)`)
|
|
|
|
func WellKnownWebfinger(w http.ResponseWriter, r *http.Request) {
|
|
type OutboundLink struct {
|
|
Relation string `json:"rel"`
|
|
Type string `json:"type"`
|
|
Href *string `json:"href,omitempty"`
|
|
}
|
|
type Outbound struct {
|
|
Subject string `json:"subject"`
|
|
Links []OutboundLink `json:"links"`
|
|
Aliases []string `json:"aliases,omitempty"`
|
|
}
|
|
log := hlog.FromRequest(r)
|
|
requestedResource := r.FormValue("resource")
|
|
matches := webfingerResourceRegex.FindStringSubmatch(requestedResource)
|
|
if len(matches) == 0 {
|
|
webutils.ProblemDetails(
|
|
w,
|
|
http.StatusBadRequest,
|
|
"/errors/webfinger-bad-resource",
|
|
"Bad resource format",
|
|
other.IntoPointer(
|
|
`The "resource" parameter must be available and of the form "acct:<username>@<domain>`,
|
|
),
|
|
nil,
|
|
)
|
|
return
|
|
}
|
|
// NOTE: Safe to access since, if the regex matches, it must include both groups
|
|
// Index 0 is the full matching string
|
|
// Index 1 is the username
|
|
// Index 2 is something vaguely domain-like
|
|
username := matches[1]
|
|
domain := matches[2]
|
|
// Fail if requested user is a different domain
|
|
// TODO: Decide whether to include the info that it's a different domain
|
|
if domain != config.GlobalConfig.General.GetFullDomain() {
|
|
webutils.ProblemDetailsStatusOnly(w, 404)
|
|
return
|
|
}
|
|
user, err := dbgen.User.GetByUsername(username)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
webutils.ProblemDetailsStatusOnly(w, 404)
|
|
} else {
|
|
// Fail the request, then attempt to reconnect
|
|
webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
|
|
if storage.HandleReconnectError(err) {
|
|
log.Warn().Msg("Connection to db lost. Reconnect attempt started")
|
|
} else {
|
|
log.Error().Err(err).Msg("Failed to get user from db")
|
|
}
|
|
}
|
|
return
|
|
}
|
|
data := Outbound{
|
|
Subject: matches[0],
|
|
Links: []OutboundLink{
|
|
{
|
|
Relation: "self",
|
|
Type: "application/activity+json",
|
|
Href: other.IntoPointer(
|
|
fmt.Sprintf(
|
|
"%s/api/activitypub/user/%s",
|
|
config.GlobalConfig.General.GetFullPublicUrl(),
|
|
user.ID,
|
|
),
|
|
),
|
|
},
|
|
{
|
|
Relation: "http://webfinger.net/rel/profile-page",
|
|
Type: "text/html",
|
|
Href: other.IntoPointer(
|
|
fmt.Sprintf(
|
|
"%s/user/%s",
|
|
config.GlobalConfig.General.GetFullPublicUrl(),
|
|
user.ID,
|
|
),
|
|
),
|
|
},
|
|
},
|
|
}
|
|
webutils.SendJson(w, &data)
|
|
}
|
|
|
|
func NodeInfoOverview(w http.ResponseWriter, r *http.Request) {
|
|
data := webshared.NodeInfoOverview{
|
|
Links: []webshared.NodeInfoLink{
|
|
{
|
|
Rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
|
|
Href: config.GlobalConfig.General.GetFullPublicUrl() + "/nodeinfo/2.1",
|
|
},
|
|
{
|
|
Rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
|
Href: config.GlobalConfig.General.GetFullPublicUrl() + "/nodeinfo/2.0",
|
|
},
|
|
},
|
|
}
|
|
webutils.SendJson(w, data)
|
|
}
|
|
|
|
func NodeInfo21(w http.ResponseWriter, r *http.Request) {
|
|
u := dbgen.User
|
|
log := hlog.FromRequest(r)
|
|
userCount, err := u.Where(u.DeletedAt.IsNull(), u.Verified.Is(true)).Count()
|
|
if err != nil {
|
|
webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
|
|
if storage.HandleReconnectError(err) {
|
|
log.Warn().Msg("Connection to db lost. Reconnect attempt started")
|
|
} else {
|
|
log.Error().Err(err).Msg("Failed to get total user count from db")
|
|
}
|
|
return
|
|
}
|
|
n := dbgen.Note
|
|
noteCount, err := n.Where(n.DeletedAt.IsNull(), n.OriginId.Eq(1)).Count()
|
|
if err != nil {
|
|
webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
|
|
if storage.HandleReconnectError(err) {
|
|
log.Warn().Msg("Connection to db lost. Reconnect attempt started")
|
|
} else {
|
|
log.Error().Err(err).Msg("Failed to get total user count from db")
|
|
}
|
|
return
|
|
}
|
|
|
|
data := webshared.NodeInfo2{
|
|
Version: "2.1",
|
|
Software: webshared.NodeInfo2Software{
|
|
Name: "linstrom",
|
|
Version: shared.Version,
|
|
Homepage: other.IntoPointer("https://git.mstar.dev/mstar/linstrom"),
|
|
Repository: other.IntoPointer("https://git.mstar.dev/mstar/linstrom"),
|
|
},
|
|
Protocols: []string{"activitypub"},
|
|
Services: map[string][]string{
|
|
"inbound": {},
|
|
"outbound": {},
|
|
},
|
|
OpenRegistrations: config.GlobalConfig.Admin.AllowRegistration,
|
|
Usage: webshared.NodeInfo2Usage{
|
|
Users: webshared.NodeInfo2UsageUsers{
|
|
Total: uint(userCount),
|
|
ActiveHalfYear: nil,
|
|
ActiveMonth: nil,
|
|
},
|
|
LocalPosts: uint(noteCount),
|
|
LocalComments: 0},
|
|
Metadata: map[string]any{},
|
|
}
|
|
webutils.SendJson(w, data)
|
|
}
|
|
|
|
func NodeInfo20(w http.ResponseWriter, r *http.Request) {
|
|
u := dbgen.User
|
|
log := hlog.FromRequest(r)
|
|
userCount, err := u.Where(u.DeletedAt.IsNull(), u.Verified.Is(true)).Count()
|
|
if err != nil {
|
|
webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
|
|
if storage.HandleReconnectError(err) {
|
|
log.Warn().Msg("Connection to db lost. Reconnect attempt started")
|
|
} else {
|
|
log.Error().Err(err).Msg("Failed to get total user count from db")
|
|
}
|
|
return
|
|
}
|
|
n := dbgen.Note
|
|
noteCount, err := n.Where(n.DeletedAt.IsNull(), n.OriginId.Eq(1)).Count()
|
|
if err != nil {
|
|
webutils.ProblemDetails(w, 500, "/errors/db-failure", "internal database failure", nil, nil)
|
|
if storage.HandleReconnectError(err) {
|
|
log.Warn().Msg("Connection to db lost. Reconnect attempt started")
|
|
} else {
|
|
log.Error().Err(err).Msg("Failed to get total user count from db")
|
|
}
|
|
return
|
|
}
|
|
data := webshared.NodeInfo2{
|
|
Version: "2.1",
|
|
Software: webshared.NodeInfo2Software{
|
|
Name: "linstrom",
|
|
Version: shared.Version,
|
|
Homepage: nil,
|
|
Repository: nil,
|
|
},
|
|
Protocols: []string{"activitypub"},
|
|
Services: map[string][]string{
|
|
"inbound": {},
|
|
"outbound": {},
|
|
},
|
|
OpenRegistrations: config.GlobalConfig.Admin.AllowRegistration,
|
|
Usage: webshared.NodeInfo2Usage{
|
|
Users: webshared.NodeInfo2UsageUsers{
|
|
Total: uint(userCount),
|
|
ActiveHalfYear: nil,
|
|
ActiveMonth: nil,
|
|
},
|
|
LocalPosts: uint(noteCount),
|
|
LocalComments: 0},
|
|
Metadata: map[string]any{},
|
|
}
|
|
|
|
webutils.SendJson(w, data)
|
|
}
|