API: implement user account deletion
This commit is contained in:
parent
a26c7a828d
commit
0fb8803a60
|
@ -0,0 +1,53 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.sr.ht/~sircmpwn/core-go/database"
|
||||||
|
work "git.sr.ht/~sircmpwn/dowork"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctxKey = &contextKey{"account"}
|
||||||
|
|
||||||
|
func Middleware(queue *work.Queue) func(next http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.WithValue(r.Context(), ctxKey, queue)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedules a user account deletion.
|
||||||
|
func Delete(ctx context.Context, userID int, username string) {
|
||||||
|
queue, ok := ctx.Value(ctxKey).(*work.Queue)
|
||||||
|
if !ok {
|
||||||
|
panic("No account worker for this context")
|
||||||
|
}
|
||||||
|
|
||||||
|
task := work.NewTask(func(ctx context.Context) error {
|
||||||
|
log.Printf("Processing deletion of user account %d %s", userID, username)
|
||||||
|
|
||||||
|
if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
|
||||||
|
_, err := tx.ExecContext(ctx, `
|
||||||
|
DELETE FROM "user" WHERE id = $1;
|
||||||
|
`, userID)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Deletion of user account %d %s complete", userID, username)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
queue.Enqueue(task)
|
||||||
|
log.Printf("Enqueued deletion of user account %d %s", userID, username)
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sr.ht/~sircmpwn/core-go v0.0.0-20221025082458-3e69641ef307
|
git.sr.ht/~sircmpwn/core-go v0.0.0-20221025082458-3e69641ef307
|
||||||
|
git.sr.ht/~sircmpwn/dowork v0.0.0-20210820133136-d3970e97def3
|
||||||
github.com/99designs/gqlgen v0.17.20
|
github.com/99designs/gqlgen v0.17.20
|
||||||
github.com/Masterminds/squirrel v1.5.0
|
github.com/Masterminds/squirrel v1.5.0
|
||||||
github.com/go-chi/chi v4.1.2+incompatible
|
github.com/go-chi/chi v4.1.2+incompatible
|
||||||
|
@ -14,7 +15,6 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sr.ht/~sircmpwn/dowork v0.0.0-20210820133136-d3970e97def3 // indirect
|
|
||||||
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 // indirect
|
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 // indirect
|
||||||
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210227202403-5dae5c48f917 // indirect
|
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210227202403-5dae5c48f917 // indirect
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
|
||||||
|
|
|
@ -21,6 +21,12 @@ directive @private on FIELD_DEFINITION
|
||||||
"Used to provide a human-friendly description of an access scope."
|
"Used to provide a human-friendly description of an access scope."
|
||||||
directive @scopehelp(details: String!) on ENUM_VALUE
|
directive @scopehelp(details: String!) on ENUM_VALUE
|
||||||
|
|
||||||
|
"""
|
||||||
|
This used to decorate fields which are for internal use, and are not
|
||||||
|
available to normal API users.
|
||||||
|
"""
|
||||||
|
directive @internal on FIELD_DEFINITION
|
||||||
|
|
||||||
enum AccessScope {
|
enum AccessScope {
|
||||||
PROFILE @scopehelp(details: "profile information")
|
PROFILE @scopehelp(details: "profile information")
|
||||||
PASTES @scopehelp(details: "pastes")
|
PASTES @scopehelp(details: "pastes")
|
||||||
|
@ -288,4 +294,9 @@ type Mutation {
|
||||||
unexpected behavior with the third-party integration.
|
unexpected behavior with the third-party integration.
|
||||||
"""
|
"""
|
||||||
deleteUserWebhook(id: Int!): WebhookSubscription!
|
deleteUserWebhook(id: Int!): WebhookSubscription!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Deletes the authenticated user's account. Internal use only.
|
||||||
|
"""
|
||||||
|
deleteUser: Int! @internal
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"git.sr.ht/~sircmpwn/core-go/server"
|
"git.sr.ht/~sircmpwn/core-go/server"
|
||||||
"git.sr.ht/~sircmpwn/core-go/valid"
|
"git.sr.ht/~sircmpwn/core-go/valid"
|
||||||
corewebhooks "git.sr.ht/~sircmpwn/core-go/webhooks"
|
corewebhooks "git.sr.ht/~sircmpwn/core-go/webhooks"
|
||||||
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/account"
|
||||||
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/api"
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/api"
|
||||||
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model"
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model"
|
||||||
"git.sr.ht/~sircmpwn/paste.sr.ht/api/loaders"
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/loaders"
|
||||||
|
@ -350,6 +351,13 @@ func (r *mutationResolver) DeleteUserWebhook(ctx context.Context, id int) (model
|
||||||
return &sub, nil
|
return &sub, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteUser is the resolver for the deleteUser field.
|
||||||
|
func (r *mutationResolver) DeleteUser(ctx context.Context) (int, error) {
|
||||||
|
user := auth.ForContext(ctx)
|
||||||
|
account.Delete(ctx, user.UserID, user.Username)
|
||||||
|
return user.UserID, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Files is the resolver for the files field.
|
// Files is the resolver for the files field.
|
||||||
func (r *pasteResolver) Files(ctx context.Context, obj *model.Paste) ([]*model.File, error) {
|
func (r *pasteResolver) Files(ctx context.Context, obj *model.Paste) ([]*model.File, error) {
|
||||||
var files []*model.File
|
var files []*model.File
|
||||||
|
|
|
@ -9,9 +9,11 @@ import (
|
||||||
"git.sr.ht/~sircmpwn/core-go/database"
|
"git.sr.ht/~sircmpwn/core-go/database"
|
||||||
"git.sr.ht/~sircmpwn/core-go/server"
|
"git.sr.ht/~sircmpwn/core-go/server"
|
||||||
"git.sr.ht/~sircmpwn/core-go/webhooks"
|
"git.sr.ht/~sircmpwn/core-go/webhooks"
|
||||||
|
work "git.sr.ht/~sircmpwn/dowork"
|
||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
|
||||||
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/account"
|
||||||
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph"
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph"
|
||||||
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/api"
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/api"
|
||||||
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model"
|
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model"
|
||||||
|
@ -23,6 +25,7 @@ func main() {
|
||||||
|
|
||||||
gqlConfig := api.Config{Resolvers: &graph.Resolver{}}
|
gqlConfig := api.Config{Resolvers: &graph.Resolver{}}
|
||||||
gqlConfig.Directives.Private = server.Private
|
gqlConfig.Directives.Private = server.Private
|
||||||
|
gqlConfig.Directives.Internal = server.Internal
|
||||||
gqlConfig.Directives.Access = func(ctx context.Context, obj interface{},
|
gqlConfig.Directives.Access = func(ctx context.Context, obj interface{},
|
||||||
next graphql.Resolver, scope model.AccessScope,
|
next graphql.Resolver, scope model.AccessScope,
|
||||||
kind model.AccessKind) (interface{}, error) {
|
kind model.AccessKind) (interface{}, error) {
|
||||||
|
@ -35,13 +38,18 @@ func main() {
|
||||||
scopes[i] = s.String()
|
scopes[i] = s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accountQueue := work.NewQueue("account")
|
||||||
webhookQueue := webhooks.NewQueue(schema)
|
webhookQueue := webhooks.NewQueue(schema)
|
||||||
|
|
||||||
gsrv := server.NewServer("paste.sr.ht", appConfig).
|
gsrv := server.NewServer("paste.sr.ht", appConfig).
|
||||||
WithDefaultMiddleware().
|
WithDefaultMiddleware().
|
||||||
WithMiddleware(loaders.Middleware, webhooks.Middleware(webhookQueue)).
|
WithMiddleware(
|
||||||
|
loaders.Middleware,
|
||||||
|
account.Middleware(accountQueue),
|
||||||
|
webhooks.Middleware(webhookQueue),
|
||||||
|
).
|
||||||
WithSchema(schema, scopes).
|
WithSchema(schema, scopes).
|
||||||
WithQueues(webhookQueue.Queue)
|
WithQueues(accountQueue, webhookQueue.Queue)
|
||||||
|
|
||||||
// Bulk transfer endpoints
|
// Bulk transfer endpoints
|
||||||
gsrv.Router().Get("/query/blob/{id}", func(w http.ResponseWriter, r *http.Request) {
|
gsrv.Router().Get("/query/blob/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
Loading…
Reference in New Issue