374 lines
11 KiB
Go
374 lines
11 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/google/jsonapi"
|
|
"github.com/rs/zerolog/hlog"
|
|
"git.mstar.dev/mstar/goutils/other"
|
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
|
"git.mstar.dev/mstar/linstrom/storage"
|
|
)
|
|
|
|
// No create account. That happens during passkey registration
|
|
// and remote accounts are getting created at fetch time
|
|
func linstromGetAccount(w http.ResponseWriter, r *http.Request) {
|
|
store := StorageFromRequest(r)
|
|
log := hlog.FromRequest(r)
|
|
|
|
accId := AccountIdFromRequest(r)
|
|
acc, err := store.FindAccountById(accId)
|
|
switch err {
|
|
case nil:
|
|
// Ok, do nothing
|
|
case storage.ErrEntryNotFound:
|
|
other.HttpErr(w, HttpErrIdNotFound, "account not found", http.StatusNotFound)
|
|
return
|
|
default:
|
|
log.Error().Err(err).Str("account-id", accId).Msg("Failed to get account from storage")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to get account from storage",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
actorId, ok := r.Context().Value(ContextKeyActorId).(string)
|
|
if ok {
|
|
// Logged in user is accessing account, check if target account has them blocked
|
|
roles, err := store.FindRolesByNames(acc.Roles)
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Strs("role-names", acc.Roles).
|
|
Msg("Failed to get roles from storage")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to get roles of target account",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
collapsedRole := storage.CollapseRolesIntoOne(roles...)
|
|
if sliceutils.Contains(collapsedRole.BlockedUsers, actorId) {
|
|
// Actor account is in list of blocked accounts, deny access
|
|
other.HttpErr(w, HttpErrIdNotAuthenticated, "Access forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
}
|
|
|
|
outAccount, err := convertAccountStorageToLinstrom(acc, store)
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Msg("Failed to convert storage account (and attached data) into linstrom API representation")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdConversionFailure,
|
|
"Failed to convert storage account and attached data into API representation",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
err = jsonapi.MarshalPayload(w, outAccount)
|
|
if err != nil {
|
|
log.Error().Err(err).Any("account", outAccount).Msg("Failed to marshal and write account")
|
|
}
|
|
}
|
|
|
|
func linstromUpdateAccount(w http.ResponseWriter, r *http.Request) {
|
|
store := StorageFromRequest(r)
|
|
log := hlog.FromRequest(r)
|
|
// Assumption: There must be a valid session once this function is called due to middlewares
|
|
actorId, _ := ActorIdFromRequest(r)
|
|
apiTarget := linstromAccount{}
|
|
err := jsonapi.UnmarshalPayload(r.Body, &apiTarget)
|
|
if err != nil {
|
|
other.HttpErr(w, HttpErrIdBadRequest, "bad body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
targetAccId := AccountIdFromRequest(r)
|
|
if apiTarget.Id != targetAccId {
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdBadRequest,
|
|
"Provided entity's id doesn't match path id",
|
|
http.StatusConflict,
|
|
)
|
|
return
|
|
}
|
|
if !(actorId == apiTarget.Id) {
|
|
other.HttpErr(w, HttpErrIdNotAuthenticated, "Invalid permissions", http.StatusForbidden)
|
|
return
|
|
}
|
|
dbTarget, err := store.FindAccountById(apiTarget.Id)
|
|
// Assumption: The only sort of errors that can be returned are db failures.
|
|
// The account not existing is not possible anymore since this is in a valid session
|
|
// and a session is only injected if the actor account can be found
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Str("account-id", actorId).
|
|
Msg("Failed to get account from db despite valid session")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to get account despite valid session",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
|
|
// location, birthday, icon, banner, background, custom fields
|
|
// bluesky federation, uhhh
|
|
|
|
dbTarget.DisplayName = apiTarget.DisplayName
|
|
dbTarget.Indexable = apiTarget.Indexable
|
|
dbTarget.Description = apiTarget.Description
|
|
// TODO: Figure out how to properly update custom fields
|
|
dbTarget.Gender = apiTarget.Pronouns
|
|
dbTarget.IdentifiesAs = sliceutils.Map(
|
|
sliceutils.Filter(apiTarget.IdentifiesAs, func(t string) bool {
|
|
return storage.IsValidBeing(t)
|
|
}),
|
|
func(t string) storage.Being { return storage.Being(t) },
|
|
)
|
|
dbTarget.Indexable = apiTarget.Indexable
|
|
dbTarget.RestrictedFollow = apiTarget.RestrictedFollow
|
|
err = store.UpdateAccount(dbTarget)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to update account in db")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to update db entries",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
newAccData, err := convertAccountStorageToLinstrom(dbTarget, store)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to convert updated account back into api form")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdConversionFailure,
|
|
"Failed to convert updated account back into api form",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
err = jsonapi.MarshalPayload(w, newAccData)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Failed to marshal and write updated account")
|
|
}
|
|
}
|
|
|
|
func linstromDeleteAccount(w http.ResponseWriter, r *http.Request) {
|
|
actorId, _ := ActorIdFromRequest(r)
|
|
log := hlog.FromRequest(r)
|
|
store := StorageFromRequest(r)
|
|
targetAccountId := AccountIdFromRequest(r)
|
|
if targetAccountId != actorId {
|
|
log.Debug().
|
|
Str("actor-id", actorId).
|
|
Str("target-id", targetAccountId).
|
|
Msg("Invalid attempt to delete account")
|
|
other.HttpErr(w, HttpErrIdNotAuthenticated, "Action forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
log.Info().Str("account-id", actorId).Msg("Deleting account")
|
|
acc, err := store.FindAccountById(targetAccountId)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("account-id", actorId).Msg("Failed to get account for deletion")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to get account from db",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
// allRoles, err := store.FindRolesByNames(acc.Roles)
|
|
// collapsedRole := storage.CollapseRolesIntoOne(allRoles...)
|
|
|
|
// TODO: Start job of sending out deletion messages to all federated servers
|
|
|
|
// Clean up related data first
|
|
// TODO: Also delete media files
|
|
err = store.DeleteRoleByName(acc.ID)
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Str("role-name", acc.ID).
|
|
Msg("Failed to delete user role for account deletion request")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to delete user role",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
err = store.DeleteAllUserFieldsForAccountId(acc.ID)
|
|
if err != nil {
|
|
log.Error().
|
|
Err(err).
|
|
Str("account-id", acc.ID).
|
|
Msg("Failed to delete custom info fields for account deletion")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to delete custom info fields",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
err = store.DeleteAccount(actorId)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("account-id", acc.ID).Msg("Failed to delete account")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to delete account from db",
|
|
http.StatusInternalServerError,
|
|
)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Is logged in following accountId
|
|
func linstromIsFollowingToAccount(w http.ResponseWriter, r *http.Request) {
|
|
store := StorageFromRequest(r)
|
|
log := hlog.FromRequest(r)
|
|
actorId, _ := ActorIdFromRequest(r)
|
|
targetId := AccountIdFromRequest(r)
|
|
|
|
relation, err := store.GetRelationBetween(actorId, targetId)
|
|
var outData linstromRelation
|
|
switch err {
|
|
case nil:
|
|
outData = linstromRelation{
|
|
Id: relation.ID,
|
|
CreatedAt: relation.CreatedAt,
|
|
UpdatedAt: relation.UpdatedAt,
|
|
FromId: relation.FromId,
|
|
ToId: relation.ToId,
|
|
Accepted: relation.Accepted,
|
|
Requested: true,
|
|
}
|
|
case storage.ErrEntryNotFound:
|
|
outData = linstromRelation{
|
|
Id: relation.ID,
|
|
CreatedAt: relation.CreatedAt,
|
|
UpdatedAt: relation.UpdatedAt,
|
|
FromId: relation.FromId,
|
|
ToId: relation.ToId,
|
|
Accepted: false,
|
|
Requested: false,
|
|
}
|
|
default:
|
|
log.Error().
|
|
Err(err).
|
|
Str("from-id", actorId).
|
|
Str("to-id", targetId).
|
|
Msg("Failed to get follow relation")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to get relation",
|
|
http.StatusInternalServerError,
|
|
)
|
|
}
|
|
|
|
err = jsonapi.MarshalPayload(w, outData)
|
|
if err != nil {
|
|
log.Warn().Err(err).Msg("Failed to marshal response")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdJsonMarshalFail,
|
|
"Failed to marshal response",
|
|
http.StatusInternalServerError,
|
|
)
|
|
}
|
|
}
|
|
|
|
func linstromIsFollowingFromAccount(w http.ResponseWriter, r *http.Request) {
|
|
store := StorageFromRequest(r)
|
|
log := hlog.FromRequest(r)
|
|
actorId, _ := ActorIdFromRequest(r)
|
|
targetId := AccountIdFromRequest(r)
|
|
|
|
relation, err := store.GetRelationBetween(targetId, actorId)
|
|
var outData linstromRelation
|
|
switch err {
|
|
case nil:
|
|
outData = linstromRelation{
|
|
Id: relation.ID,
|
|
CreatedAt: relation.CreatedAt,
|
|
UpdatedAt: relation.UpdatedAt,
|
|
FromId: relation.FromId,
|
|
ToId: relation.ToId,
|
|
Accepted: relation.Accepted,
|
|
Requested: true,
|
|
}
|
|
case storage.ErrEntryNotFound:
|
|
outData = linstromRelation{
|
|
Id: relation.ID,
|
|
CreatedAt: relation.CreatedAt,
|
|
UpdatedAt: relation.UpdatedAt,
|
|
FromId: relation.FromId,
|
|
ToId: relation.ToId,
|
|
Accepted: false,
|
|
Requested: false,
|
|
}
|
|
default:
|
|
log.Error().
|
|
Err(err).
|
|
Str("from-id", targetId).
|
|
Str("to-id", actorId).
|
|
Msg("Failed to get follow relation")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdDbFailure,
|
|
"Failed to get relation",
|
|
http.StatusInternalServerError,
|
|
)
|
|
}
|
|
|
|
err = jsonapi.MarshalPayload(w, outData)
|
|
if err != nil {
|
|
log.Warn().Err(err).Msg("Failed to marshal response")
|
|
other.HttpErr(
|
|
w,
|
|
HttpErrIdJsonMarshalFail,
|
|
"Failed to marshal response",
|
|
http.StatusInternalServerError,
|
|
)
|
|
}
|
|
}
|
|
|
|
func linstromFollowAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromUnfollowAccount(w http.ResponseWriter, r *http.Request) {}
|
|
|
|
func linstromIsBlockingAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromBlockAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromUnblockAccount(w http.ResponseWriter, r *http.Request) {}
|
|
|
|
func linstromIsMutedAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromMuteAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromUnmuteAccount(w http.ResponseWriter, r *http.Request) {}
|
|
|
|
func linstromReportAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromRetractReportAccount(w http.ResponseWriter, r *http.Request) {}
|
|
|
|
func linstromAdminAddRoleAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromAdminRemoveRoleAccount(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromAdminWarnAccount(w http.ResponseWriter, r *http.Request) {}
|
|
|
|
func linstromGetRole(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromCreateRole(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromUpdateRole(w http.ResponseWriter, r *http.Request) {}
|
|
func linstromDeleteRole(w http.ResponseWriter, r *http.Request) {}
|