forked from mirrors/pronouns.cc
Compare commits
6 commits
main
...
feature/be
Author | SHA1 | Date | |
---|---|---|---|
|
98ebbf0615 | ||
|
dcc0c24f82 | ||
|
73d132eb4c | ||
|
2dd37f15bf | ||
|
9726827706 | ||
|
c8066be0a6 |
14 changed files with 943 additions and 83 deletions
|
@ -37,3 +37,15 @@ func (id FlagID) Increment() uint16 { return Snowflake(id).Increment() }
|
||||||
|
|
||||||
func (id FlagID) MarshalJSON() ([]byte, error) { return Snowflake(id).MarshalJSON() }
|
func (id FlagID) MarshalJSON() ([]byte, error) { return Snowflake(id).MarshalJSON() }
|
||||||
func (id *FlagID) UnmarshalJSON(src []byte) error { return (*Snowflake)(id).UnmarshalJSON(src) }
|
func (id *FlagID) UnmarshalJSON(src []byte) error { return (*Snowflake)(id).UnmarshalJSON(src) }
|
||||||
|
|
||||||
|
type AuditLogID Snowflake
|
||||||
|
|
||||||
|
func (id AuditLogID) String() string { return Snowflake(id).String() }
|
||||||
|
func (id AuditLogID) Time() time.Time { return Snowflake(id).Time() }
|
||||||
|
func (id AuditLogID) IsValid() bool { return Snowflake(id).IsValid() }
|
||||||
|
func (id AuditLogID) Worker() uint8 { return Snowflake(id).Worker() }
|
||||||
|
func (id AuditLogID) PID() uint8 { return Snowflake(id).PID() }
|
||||||
|
func (id AuditLogID) Increment() uint16 { return Snowflake(id).Increment() }
|
||||||
|
|
||||||
|
func (id AuditLogID) MarshalJSON() ([]byte, error) { return Snowflake(id).MarshalJSON() }
|
||||||
|
func (id *AuditLogID) UnmarshalJSON(src []byte) error { return (*Snowflake)(id).UnmarshalJSON(src) }
|
||||||
|
|
84
backend/db/audit_log.go
Normal file
84
backend/db/audit_log.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/common"
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/georgysavva/scany/v2/pgxscan"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuditLogEntry struct {
|
||||||
|
ID common.AuditLogID
|
||||||
|
TargetUserID common.UserID
|
||||||
|
TargetMemberID *common.MemberID
|
||||||
|
ModeratorID common.UserID
|
||||||
|
ReportID *int64
|
||||||
|
|
||||||
|
Reason string
|
||||||
|
ActionTaken string
|
||||||
|
ClearedData *AuditLogClearedData
|
||||||
|
|
||||||
|
TargetUsername *string
|
||||||
|
TargetMemberName *string
|
||||||
|
ModeratorUsername *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuditLogClearedData struct {
|
||||||
|
DisplayName *string `json:"display_name,omitempty"`
|
||||||
|
Bio *string `json:"bio,omitempty"`
|
||||||
|
Links []string `json:"links,omitempty"`
|
||||||
|
Names []FieldEntry `json:"names,omitempty"`
|
||||||
|
Pronouns []PronounEntry `json:"pronouns,omitempty"`
|
||||||
|
Fields []Field `json:"fields,omitempty"`
|
||||||
|
CustomPreferences []CustomPreference `json:"custom_preferences"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a max of 100 audit log entries created before the time in `before`.
|
||||||
|
// If `before` is 0, returns the latest entries.
|
||||||
|
func (db *DB) AuditLog(ctx context.Context, before common.AuditLogID) (es []AuditLogEntry, err error) {
|
||||||
|
b := sq.Select("a.*", "u1.username as target_username", "u2.username as moderator_username", "m.name as target_member_name").
|
||||||
|
From("audit_log a").Limit(100).OrderBy("id DESC").
|
||||||
|
LeftJoin("users u1 ON a.target_user_id = u1.snowflake_id").
|
||||||
|
LeftJoin("users u2 ON a.moderator_id = u2.snowflake_id").
|
||||||
|
LeftJoin("members m ON a.target_member_id = m.snowflake_id")
|
||||||
|
if before.IsValid() {
|
||||||
|
b = b.Where("id < ?", before)
|
||||||
|
}
|
||||||
|
sql, args, err := b.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "building query")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(sql)
|
||||||
|
|
||||||
|
err = pgxscan.Select(ctx, db, &es, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "executing query")
|
||||||
|
}
|
||||||
|
return NotNull(es), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) CreateAuditLogEntry(ctx context.Context, tx pgx.Tx, data AuditLogEntry) (e AuditLogEntry, err error) {
|
||||||
|
sql, args, err := sq.Insert("audit_log").SetMap(map[string]any{
|
||||||
|
"id": common.GenerateID(),
|
||||||
|
"target_user_id": data.TargetUserID,
|
||||||
|
"target_member_id": data.TargetMemberID,
|
||||||
|
"moderator_id": data.ModeratorID,
|
||||||
|
"report_id": data.ReportID,
|
||||||
|
"reason": data.Reason,
|
||||||
|
"action_taken": data.ActionTaken,
|
||||||
|
"cleared_data": data.ClearedData,
|
||||||
|
}).Suffix("RETURNING *").ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return e, errors.Wrap(err, "building query")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pgxscan.Get(ctx, tx, &e, sql, args...)
|
||||||
|
if err != nil {
|
||||||
|
return e, errors.Wrap(err, "executing query")
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
|
@ -174,7 +174,7 @@ func (db *DB) CreateWarning(ctx context.Context, tx pgx.Tx, userID xid.ID, reaso
|
||||||
sql, args, err := sq.Insert("warnings").SetMap(map[string]any{
|
sql, args, err := sq.Insert("warnings").SetMap(map[string]any{
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
}).ToSql()
|
}).Suffix("RETURNING *").ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return w, errors.Wrap(err, "building sql")
|
return w, errors.Wrap(err, "building sql")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/routes/v1/meta"
|
"codeberg.org/pronounscc/pronouns.cc/backend/routes/v1/meta"
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/routes/v1/mod"
|
"codeberg.org/pronounscc/pronouns.cc/backend/routes/v1/mod"
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/routes/v1/user"
|
"codeberg.org/pronounscc/pronouns.cc/backend/routes/v1/user"
|
||||||
|
admin2 "codeberg.org/pronounscc/pronouns.cc/backend/routes/v2/admin"
|
||||||
user2 "codeberg.org/pronounscc/pronouns.cc/backend/routes/v2/user"
|
user2 "codeberg.org/pronounscc/pronouns.cc/backend/routes/v2/user"
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -26,5 +27,6 @@ func mountRoutes(s *server.Server) {
|
||||||
|
|
||||||
s.Router.Route("/v2", func(r chi.Router) {
|
s.Router.Route("/v2", func(r chi.Router) {
|
||||||
user2.Mount(s, r)
|
user2.Mount(s, r)
|
||||||
|
admin2.Mount(s, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
72
backend/routes/v2/admin/audit_log.go
Normal file
72
backend/routes/v2/admin/audit_log.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/common"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuditLogEntryResponse struct {
|
||||||
|
ID common.AuditLogID `json:"id"`
|
||||||
|
TargetUserID common.UserID `json:"target_user_id"`
|
||||||
|
TargetMemberID *common.MemberID `json:"target_member_id"`
|
||||||
|
ModeratorID common.UserID `json:"moderator_id"`
|
||||||
|
ReportID *int64 `json:"report_id"`
|
||||||
|
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
ActionTaken string `json:"action_taken"`
|
||||||
|
ClearedData *db.AuditLogClearedData `json:"cleared_data"`
|
||||||
|
|
||||||
|
TargetUsername *string `json:"target_name"`
|
||||||
|
TargetMemberName *string `json:"target_member_name"`
|
||||||
|
ModeratorUsername *string `json:"moderator_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbAuditLogToResponse(e db.AuditLogEntry) AuditLogEntryResponse {
|
||||||
|
return AuditLogEntryResponse{
|
||||||
|
ID: e.ID,
|
||||||
|
TargetUserID: e.TargetUserID,
|
||||||
|
TargetMemberID: e.TargetMemberID,
|
||||||
|
ModeratorID: e.ModeratorID,
|
||||||
|
ReportID: e.ReportID,
|
||||||
|
Reason: e.Reason,
|
||||||
|
ActionTaken: e.ActionTaken,
|
||||||
|
ClearedData: e.ClearedData,
|
||||||
|
TargetUsername: e.TargetUsername,
|
||||||
|
TargetMemberName: e.TargetMemberName,
|
||||||
|
ModeratorUsername: e.ModeratorUsername,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbAuditLogsToResponse(es []db.AuditLogEntry) []AuditLogEntryResponse {
|
||||||
|
resps := make([]AuditLogEntryResponse, len(es))
|
||||||
|
for i := range es {
|
||||||
|
resps[i] = dbAuditLogToResponse(es[i])
|
||||||
|
}
|
||||||
|
return resps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AuditLog(w http.ResponseWriter, r *http.Request) (err error) {
|
||||||
|
beforeID := common.AuditLogID(0)
|
||||||
|
if s := r.FormValue("before"); s != "" {
|
||||||
|
sf, err := common.ParseSnowflake(s)
|
||||||
|
if err != nil {
|
||||||
|
return server.NewV2Error(server.ErrBadRequest, "", server.NewModelParseError("query", "Invalid snowflake"))
|
||||||
|
}
|
||||||
|
beforeID = common.AuditLogID(sf)
|
||||||
|
}
|
||||||
|
|
||||||
|
es, err := s.DB.AuditLog(r.Context(), beforeID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("getting audit logs before %v: %v", beforeID, err)
|
||||||
|
return errors.Wrap(err, "getting audit logs")
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSON(w, r, dbAuditLogsToResponse(es))
|
||||||
|
return nil
|
||||||
|
}
|
55
backend/routes/v2/admin/routes.go
Normal file
55
backend/routes/v2/admin/routes.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActionTypeIgnore = "ignore"
|
||||||
|
ActionTypeWarn = "warn"
|
||||||
|
ActionTypeSuspend = "suspend"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*server.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mount(srv *server.Server, r chi.Router) {
|
||||||
|
s := &Server{Server: srv}
|
||||||
|
|
||||||
|
r.With(MustAdmin).Route("/admin", func(r chi.Router) {
|
||||||
|
r.Get("/audit-log", server.WrapHandler(s.AuditLog))
|
||||||
|
r.Post("/users/{id}/actions", server.WrapHandler(s.UserAction))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustAdmin(next http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := server.ClaimsFromContext(r.Context())
|
||||||
|
if !ok {
|
||||||
|
render.Status(r, http.StatusForbidden)
|
||||||
|
render.JSON(w, r, server.APIError{
|
||||||
|
Code: server.ErrForbidden,
|
||||||
|
Message: "Forbidden",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !claims.UserIsAdmin {
|
||||||
|
render.Status(r, http.StatusForbidden)
|
||||||
|
render.JSON(w, r, server.APIError{
|
||||||
|
Code: server.ErrForbidden,
|
||||||
|
Message: "Forbidden",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
375
backend/routes/v2/admin/user_action.go
Normal file
375
backend/routes/v2/admin/user_action.go
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/common"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/log"
|
||||||
|
"codeberg.org/pronounscc/pronouns.cc/backend/server"
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"github.com/aarondl/opt/omit"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const MaxReasonLength = 2048
|
||||||
|
|
||||||
|
type UserActionRequest struct {
|
||||||
|
Type string `json:"type"` // `ignore`, `warn`, `suspend`
|
||||||
|
ReportID omit.Val[int64] `json:"report_id"` // The report ID associated with this action, only required if type is ignore. Will close this report.
|
||||||
|
Reason string `json:"reason"` // The reason for the action. Always logged for moderators, only shown to user for `warn` and `suspend`
|
||||||
|
Clear struct { // Profile fields to clear
|
||||||
|
DisplayName bool `json:"display_name"`
|
||||||
|
Bio bool `json:"bio"`
|
||||||
|
Links bool `json:"links"`
|
||||||
|
|
||||||
|
Names []int `json:"names"` // Indexes of names to clear
|
||||||
|
Pronouns []int `json:"pronouns"` // Indexes of pronouns to clear
|
||||||
|
Fields []int `json:"fields"` // Indexes of fields to clear
|
||||||
|
CustomPreferences []uuid.UUID `json:"custom_preferences"` // Custom preference IDs to clear
|
||||||
|
// TODO: remove flags, maybe?
|
||||||
|
} `json:"clear"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserActionResponse struct {
|
||||||
|
AuditLogID common.AuditLogID `json:"audit_log_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req UserActionRequest) Validate(u db.User, fields []db.Field) (errs []server.ModelParseError) {
|
||||||
|
switch req.Type {
|
||||||
|
case ActionTypeIgnore, ActionTypeWarn, ActionTypeSuspend:
|
||||||
|
default:
|
||||||
|
errs = append(errs,
|
||||||
|
server.NewModelParseErrorWithValues("type", "", []any{ActionTypeIgnore, ActionTypeSuspend, ActionTypeWarn}, req.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type != ActionTypeIgnore && req.Reason == "" {
|
||||||
|
errs = append(errs, server.NewModelParseError("reason", "reason cannot be empty if type is not ignore"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type == ActionTypeIgnore && req.ReportID.IsUnset() {
|
||||||
|
errs = append(errs, server.NewModelParseError("report_id", "report_id cannot be empty if type is ignore"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type == ActionTypeIgnore &&
|
||||||
|
(req.Clear.DisplayName || req.Clear.Bio || req.Clear.Links ||
|
||||||
|
len(req.Clear.Names) != 0 || len(req.Clear.Pronouns) != 0 ||
|
||||||
|
len(req.Clear.Fields) != 0 || len(req.Clear.CustomPreferences) != 0) {
|
||||||
|
|
||||||
|
errs = append(errs, server.NewModelParseError("clear", "clear cannot be set if report is being ignored"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check names
|
||||||
|
if len(req.Clear.Names) > len(u.Names) {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.names", "cannot have more indexes than there are names"))
|
||||||
|
}
|
||||||
|
if !allUnique(req.Clear.Names) {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.names", "all indexes in clear.names must be unique"))
|
||||||
|
}
|
||||||
|
namesOutOfRange := false
|
||||||
|
for _, i := range req.Clear.Names {
|
||||||
|
if i >= len(u.Names) {
|
||||||
|
namesOutOfRange = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if namesOutOfRange {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.names", "one or more indexes is out of range"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check pronouns
|
||||||
|
if len(req.Clear.Pronouns) > len(u.Pronouns) {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.pronouns", "cannot have more indexes than there are pronouns"))
|
||||||
|
}
|
||||||
|
if !allUnique(req.Clear.Pronouns) {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.pronouns", "all indexes in clear.pronouns must be unique"))
|
||||||
|
}
|
||||||
|
pronounsOutOfRange := false
|
||||||
|
for _, i := range req.Clear.Pronouns {
|
||||||
|
if i >= len(u.Pronouns) {
|
||||||
|
pronounsOutOfRange = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pronounsOutOfRange {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.pronouns", "one or more indexes is out of range"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check fields
|
||||||
|
if len(req.Clear.Fields) > len(fields) {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.fields", "cannot have more indexes than there are fields"))
|
||||||
|
}
|
||||||
|
if !allUnique(req.Clear.Fields) {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.fields", "all indexes in clear.fields must be unique"))
|
||||||
|
}
|
||||||
|
fieldsOutOfRange := false
|
||||||
|
for _, i := range req.Clear.Fields {
|
||||||
|
if i >= len(fields) {
|
||||||
|
fieldsOutOfRange = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fieldsOutOfRange {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.fields", "one or more indexes is out of range"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check custom preferences
|
||||||
|
if !allUnique(req.Clear.CustomPreferences) {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.custom_preferences", "all IDs in clear.custom_preferences must be unique"))
|
||||||
|
}
|
||||||
|
invalidIDs := false
|
||||||
|
for _, id := range req.Clear.CustomPreferences {
|
||||||
|
if _, ok := u.CustomPreferences[id.String()]; !ok {
|
||||||
|
invalidIDs = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if invalidIDs {
|
||||||
|
errs = append(errs, server.NewModelParseError("clear.custom_preferences", "unknown ID specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req UserActionRequest) HasClear() bool {
|
||||||
|
return req.Clear.DisplayName || req.Clear.Bio || req.Clear.Links ||
|
||||||
|
len(req.Clear.Names) != 0 || len(req.Clear.Pronouns) != 0 ||
|
||||||
|
len(req.Clear.Fields) != 0 || len(req.Clear.CustomPreferences) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UserAction(w http.ResponseWriter, r *http.Request) (err error) {
|
||||||
|
ctx := r.Context()
|
||||||
|
claims, _ := server.ClaimsFromContext(ctx)
|
||||||
|
mod, err := s.DB.User(ctx, claims.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting moderator data")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the user we're taking action against
|
||||||
|
target, err := s.ParseUser(ctx, chi.URLParam(r, "id"))
|
||||||
|
if err != nil {
|
||||||
|
return server.NewV2Error(server.ErrUserNotFound, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fields, err := s.DB.UserFields(ctx, target.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("getting fields for user %v: %v", target.SnowflakeID, fields)
|
||||||
|
return server.NewV2Error(server.ErrInternalServerError, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := server.Decode[UserActionRequest](r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// validate input
|
||||||
|
if errs := req.Validate(target, fields); len(errs) != 0 {
|
||||||
|
return server.NewV2Error(server.ErrBadRequest, "", errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store removed names for the audit log
|
||||||
|
var (
|
||||||
|
deletedNames []db.FieldEntry
|
||||||
|
deletedPronouns []db.PronounEntry
|
||||||
|
deletedFields []db.Field
|
||||||
|
deletedPreferences []db.CustomPreference
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove names
|
||||||
|
// Sort to reverse order, so elements don't shift indexes before we remove them
|
||||||
|
sort.Sort(sort.Reverse(sort.IntSlice(req.Clear.Names)))
|
||||||
|
for _, i := range req.Clear.Names {
|
||||||
|
var deleted db.FieldEntry
|
||||||
|
target.Names, deleted = deleteIndex(target.Names, i)
|
||||||
|
deletedNames = append(deletedNames, deleted)
|
||||||
|
}
|
||||||
|
// Remove pronouns
|
||||||
|
sort.Sort(sort.Reverse(sort.IntSlice(req.Clear.Pronouns)))
|
||||||
|
for _, i := range req.Clear.Pronouns {
|
||||||
|
var deleted db.PronounEntry
|
||||||
|
target.Pronouns, deleted = deleteIndex(target.Pronouns, i)
|
||||||
|
deletedPronouns = append(deletedPronouns, deleted)
|
||||||
|
}
|
||||||
|
// Remove fields
|
||||||
|
sort.Sort(sort.Reverse(sort.IntSlice(req.Clear.Fields)))
|
||||||
|
for _, i := range req.Clear.Fields {
|
||||||
|
var deleted db.Field
|
||||||
|
fields, deleted = deleteIndex(fields, i)
|
||||||
|
deletedFields = append(deletedFields, deleted)
|
||||||
|
}
|
||||||
|
// Remove custom preferences
|
||||||
|
for _, k := range req.Clear.CustomPreferences {
|
||||||
|
deletedPreferences = append(deletedPreferences, target.CustomPreferences[k.String()])
|
||||||
|
delete(target.CustomPreferences, k.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := s.DB.Begin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("starting transaction: %v", err)
|
||||||
|
return errors.Wrap(err, "starting transaction")
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
// If report_id is set, check whether the report exists.
|
||||||
|
var reportID *int64
|
||||||
|
if id, ok := req.ReportID.Get(); ok {
|
||||||
|
reportID = &id
|
||||||
|
_, err := s.DB.Report(ctx, tx, *reportID)
|
||||||
|
if err != nil {
|
||||||
|
if err == db.ErrReportNotFound {
|
||||||
|
return server.NewV2Error(server.ErrBadRequest, "", server.NewModelParseError("report_id", "invalid report ID"))
|
||||||
|
}
|
||||||
|
log.Errorf("getting report: %v", err)
|
||||||
|
return errors.Wrap(err, "getting report")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the cleared data object
|
||||||
|
var clearedData *db.AuditLogClearedData
|
||||||
|
if req.HasClear() {
|
||||||
|
clearedData = &db.AuditLogClearedData{
|
||||||
|
Names: deletedNames,
|
||||||
|
Pronouns: deletedPronouns,
|
||||||
|
Fields: deletedFields,
|
||||||
|
CustomPreferences: deletedPreferences,
|
||||||
|
}
|
||||||
|
if req.Clear.DisplayName {
|
||||||
|
clearedData.DisplayName = target.DisplayName
|
||||||
|
}
|
||||||
|
if req.Clear.Bio {
|
||||||
|
clearedData.Bio = target.Bio
|
||||||
|
}
|
||||||
|
if req.Clear.Links {
|
||||||
|
clearedData.Links = target.Links
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there's a non-empty reason
|
||||||
|
reason := req.Reason
|
||||||
|
if req.Reason == "" {
|
||||||
|
reason = "None"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create audit log entry
|
||||||
|
auditLogEntry, err := s.DB.CreateAuditLogEntry(ctx, tx, db.AuditLogEntry{
|
||||||
|
TargetUserID: target.SnowflakeID,
|
||||||
|
TargetMemberID: nil,
|
||||||
|
ModeratorID: mod.SnowflakeID,
|
||||||
|
ReportID: reportID,
|
||||||
|
Reason: reason,
|
||||||
|
ActionTaken: req.Type,
|
||||||
|
ClearedData: clearedData,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("creating audit log entry: %v", err)
|
||||||
|
return errors.Wrap(err, "creating audit log entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve report, if an ID was given
|
||||||
|
if req.ReportID.IsSet() {
|
||||||
|
err = s.DB.ResolveReport(ctx, tx, req.ReportID.GetOrZero(), mod.ID, req.Reason)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("resolving report: %v", err)
|
||||||
|
return errors.Wrap(err, "resolving report")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user warning
|
||||||
|
if req.Type == ActionTypeWarn || req.Type == ActionTypeSuspend {
|
||||||
|
_, err = s.DB.CreateWarning(ctx, tx, target.ID, req.Reason)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("creating warning: %v", err)
|
||||||
|
return errors.Wrap(err, "creating warning")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type == ActionTypeSuspend {
|
||||||
|
err = s.DB.DeleteUser(ctx, tx, target.ID, false, req.Reason)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("suspending user: %v", err)
|
||||||
|
return errors.Wrap(err, "suspending user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're clearing some data, do that
|
||||||
|
if req.HasClear() {
|
||||||
|
var displayName *string
|
||||||
|
if req.Clear.DisplayName {
|
||||||
|
displayName = ptr("")
|
||||||
|
}
|
||||||
|
var bio *string
|
||||||
|
if req.Clear.Bio {
|
||||||
|
bio = ptr("")
|
||||||
|
}
|
||||||
|
var links *[]string
|
||||||
|
if req.Clear.Links {
|
||||||
|
links = &[]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update most columns
|
||||||
|
_, err = s.DB.UpdateUser(ctx, tx, target.ID, displayName, bio, nil, nil, links, nil, nil, &target.CustomPreferences)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("updating user: %v", err)
|
||||||
|
return errors.Wrap(err, "updating user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set fields
|
||||||
|
err = s.DB.SetUserFields(ctx, tx, target.ID, fields)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("updating fields: %v", err)
|
||||||
|
return errors.Wrap(err, "updating fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set names/pronouns
|
||||||
|
err = s.DB.SetUserNamesPronouns(ctx, tx, target.ID, target.Names, target.Pronouns)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("updating names/pronouns: %v", err)
|
||||||
|
return errors.Wrap(err, "updating names/pronouns")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit transaction and save everything
|
||||||
|
err = tx.Commit(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("committing transaction: %v", err)
|
||||||
|
return errors.Wrap(err, "committing transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSON(w, r, UserActionResponse{
|
||||||
|
AuditLogID: auditLogEntry.ID,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ParseUser(ctx context.Context, id string) (u db.User, err error) {
|
||||||
|
sf, err := common.ParseSnowflake(id)
|
||||||
|
if err != nil {
|
||||||
|
return u, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.DB.UserBySnowflake(ctx, common.UserID(sf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func allUnique[T comparable](slice []T) bool {
|
||||||
|
m := make(map[T]struct{})
|
||||||
|
for _, entry := range slice {
|
||||||
|
_, ok := m[entry]
|
||||||
|
if ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
m[entry] = struct{}{}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteIndex[T any](slice []T, idx int) ([]T, T) {
|
||||||
|
deleted := slice[idx]
|
||||||
|
return append(slice[:idx], slice[idx+1:]...), deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptr[T any](t T) *T {
|
||||||
|
return &t
|
||||||
|
}
|
121
backend/server/error_v2.go
Normal file
121
backend/server/error_v2.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
json2 "github.com/aarondl/json"
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An error returned by version 2 of the API.
|
||||||
|
type APIError2 struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
ID *sentry.EventID `json:"id,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Details string `json:"details,omitempty"`
|
||||||
|
Errors map[string][]ModelParseError `json:"errors,omitempty"`
|
||||||
|
|
||||||
|
RatelimitReset *int `json:"ratelimit_reset,omitempty"`
|
||||||
|
|
||||||
|
Status int `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e APIError2) Error() string {
|
||||||
|
if e.Message == "" {
|
||||||
|
e.Message = errCodeMessages[e.Code]
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Details != "" {
|
||||||
|
return fmt.Sprintf("%s (code: %d) (%s)", e.Message, e.Code, e.Details)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new error in APIv2 format.
|
||||||
|
func NewV2Error(code int, details string, parseErrors ...ModelParseError) APIError2 {
|
||||||
|
var errors map[string][]ModelParseError
|
||||||
|
if len(parseErrors) != 0 {
|
||||||
|
errors = make(map[string][]ModelParseError)
|
||||||
|
for _, p := range parseErrors {
|
||||||
|
errors[p.Key] = append(errors[p.Key], p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return APIError2{
|
||||||
|
Code: code,
|
||||||
|
Message: errCodeMessages[code],
|
||||||
|
Details: details,
|
||||||
|
Errors: errors,
|
||||||
|
Status: errCodeStatuses[code],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModelParseError struct {
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
MaxLength int `json:"max_length,omitempty"`
|
||||||
|
ActualLength int `json:"actual_length,omitempty"`
|
||||||
|
|
||||||
|
ExpectedValues []any `json:"expected_values,omitempty"`
|
||||||
|
ActualValue any `json:"actual_value,omitempty"`
|
||||||
|
|
||||||
|
ExpectedType string `json:"expected_type,omitempty"`
|
||||||
|
ActualType string `json:"actual_type,omitempty"`
|
||||||
|
|
||||||
|
Key string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModelParseError(key, message string) ModelParseError {
|
||||||
|
return NewModelParseErrorWithLength(key, message, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModelParseErrorWithLength(key, message string, maxLength, actualLength int) ModelParseError {
|
||||||
|
return ModelParseError{
|
||||||
|
Key: key,
|
||||||
|
Message: message,
|
||||||
|
MaxLength: maxLength,
|
||||||
|
ActualLength: actualLength,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewModelParseErrorWithValues(key, message string, expectedValues []any, actualValue any) ModelParseError {
|
||||||
|
return ModelParseError{
|
||||||
|
Key: key,
|
||||||
|
Message: message,
|
||||||
|
ExpectedValues: expectedValues,
|
||||||
|
ActualValue: actualValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseJSONError(err error) *ModelParseError {
|
||||||
|
switch pe := err.(type) {
|
||||||
|
case *json.UnmarshalTypeError:
|
||||||
|
key := pe.Field
|
||||||
|
if key == "" {
|
||||||
|
key = "parse"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ModelParseError{
|
||||||
|
Key: key,
|
||||||
|
ExpectedType: pe.Type.String(),
|
||||||
|
ActualType: pe.Value,
|
||||||
|
}
|
||||||
|
case *json2.UnmarshalTypeError:
|
||||||
|
// TODO: remove the need for this, somehow
|
||||||
|
// This seems to be a bug in the
|
||||||
|
key := pe.Field
|
||||||
|
if key == "" {
|
||||||
|
key = "parse"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ModelParseError{
|
||||||
|
Key: key,
|
||||||
|
Message: "Invalid type passed for a field. Make sure your types match those in the documentation.",
|
||||||
|
ExpectedType: pe.Type.String(),
|
||||||
|
ActualType: pe.Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -28,6 +28,10 @@ func WrapHandler(hn func(w http.ResponseWriter, r *http.Request) error) http.Han
|
||||||
if apiErr, ok := err.(APIError); ok {
|
if apiErr, ok := err.(APIError); ok {
|
||||||
apiErr.prepare()
|
apiErr.prepare()
|
||||||
|
|
||||||
|
render.Status(r, apiErr.Status)
|
||||||
|
render.JSON(w, r, apiErr)
|
||||||
|
return
|
||||||
|
} else if apiErr, ok := err.(APIError2); ok {
|
||||||
render.Status(r, apiErr.Status)
|
render.Status(r, apiErr.Status)
|
||||||
render.JSON(w, r, apiErr)
|
render.JSON(w, r, apiErr)
|
||||||
return
|
return
|
||||||
|
@ -43,7 +47,6 @@ func WrapHandler(hn func(w http.ResponseWriter, r *http.Request) error) http.Han
|
||||||
if isExpectedError(err) {
|
if isExpectedError(err) {
|
||||||
log.Infof("expected error in handler for %v %v, ignoring", rctx.RouteMethod, rctx.RoutePattern())
|
log.Infof("expected error in handler for %v %v, ignoring", rctx.RouteMethod, rctx.RoutePattern())
|
||||||
} else {
|
} else {
|
||||||
log.Errorf("error in handler for %v %v: %v", rctx.RouteMethod, rctx.RoutePattern(), err)
|
|
||||||
eventID = hub.CaptureException(err)
|
eventID = hub.CaptureException(err)
|
||||||
}
|
}
|
||||||
apiErr := APIError{ID: eventID, Code: ErrInternalServerError}
|
apiErr := APIError{ID: eventID, Code: ErrInternalServerError}
|
||||||
|
|
|
@ -154,3 +154,21 @@ type ctxKey int
|
||||||
const (
|
const (
|
||||||
ctxKeyClaims ctxKey = 1
|
ctxKeyClaims ctxKey = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Decodes a JSON request into a value of T.
|
||||||
|
// If an error is returned, transform that into a ModelParseError.
|
||||||
|
// Known issue: parse errors in `github.com/aarondl/opt` types don't return a field name,
|
||||||
|
// so those are put in the `parse` key of the returned error.
|
||||||
|
func Decode[T any](r *http.Request) (T, error) {
|
||||||
|
var t T
|
||||||
|
|
||||||
|
err := render.Decode(r, &t)
|
||||||
|
if err != nil {
|
||||||
|
pe := ParseJSONError(err)
|
||||||
|
if pe != nil {
|
||||||
|
return t, NewV2Error(ErrBadRequest, "", *pe)
|
||||||
|
}
|
||||||
|
return t, NewV2Error(ErrBadRequest, "")
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
72
docs/api/endpoints/v2/admin.md
Normal file
72
docs/api/endpoints/v2/admin.md
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
# Admin endpoints (v2)
|
||||||
|
|
||||||
|
## Audit log object
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ------------------ | ------------------- | ----------------------------------------------------------------------------------- |
|
||||||
|
| id | snowflake | This action's ID |
|
||||||
|
| target_user_id | snowflake | The target user of this action. This may not be a valid user. |
|
||||||
|
| target_member_id | snowflake? | The target member of this action. This may not be a valid member, even if not null. |
|
||||||
|
| moderator_id | snowflake | The moderator that took this action. This may not be a valid user. |
|
||||||
|
| report_id | int? | The report closed by this action |
|
||||||
|
| reason | string | The reason this action was taken |
|
||||||
|
| action_taken | string | The type of action taken. May be 'ignore', 'warn', or 'suspend'. |
|
||||||
|
| cleared_data | cleared data object | Any user/member data cleared by this action |
|
||||||
|
| target_name | string? | The target user's username |
|
||||||
|
| target_member_name | string? | The target member's name |
|
||||||
|
| moderator_name | string? | The moderator's name |
|
||||||
|
|
||||||
|
### Cleared data object
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ------------------ | --------------------------------------------- | ----------------------------------------------------------- |
|
||||||
|
| display_name | ?string | The previously set display name |
|
||||||
|
| bio | ?string | The previously set bio |
|
||||||
|
| links | ?string[] | The previously set links |
|
||||||
|
| names | ?[field_entry](../#field-entry)[] | The previously set names |
|
||||||
|
| pronouns | ?[pronoun_entry](../#pronoun-entry)[] | The previously set pronouns |
|
||||||
|
| fields | ?[field](../#field)[] | The previously set fields |
|
||||||
|
| custom_preferences | ?[custom_preference](../#custom-preference)[] | The previously set custom preferences _(user actions only)_ |
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### Get audit logs
|
||||||
|
|
||||||
|
#### `GET /admin/audit-log`
|
||||||
|
|
||||||
|
**Requires authentication.** Gets a list of audit log entries. By default, returns the latest 100 entries;
|
||||||
|
to page through results, use the `before` query parameter.
|
||||||
|
Returns an array of [audit log objects](./admin#audit-log-object) on success.
|
||||||
|
|
||||||
|
#### Query parameters
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ------ | --------- | -------------------------------------------- |
|
||||||
|
| before | snowflake | Only get entries with IDs lower than this ID |
|
||||||
|
|
||||||
|
### Take action on a user
|
||||||
|
|
||||||
|
#### `POST /admin/users/{id}/actions`
|
||||||
|
|
||||||
|
**Requires authentication.** Take moderation actions on the given user.
|
||||||
|
|
||||||
|
#### Request body parameters
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ------------------------ | ------- | ------------------------------------------------------------------------------- |
|
||||||
|
| type | string | The type of action to take. Can be 'ignore', 'warn', 'suspend'. |
|
||||||
|
| report_id | ?int | The report this action will close. Required if type is 'ignore'. |
|
||||||
|
| reason | ?string | The reason for this action. Required if type is not 'ignore'. |
|
||||||
|
| clear.display_name | bool | Whether to clear the user's display name |
|
||||||
|
| clear.bio | bool | Whether to clear the user's bio |
|
||||||
|
| clear.links | bool | Whether to clear the user's profile links |
|
||||||
|
| clear.names | int[] | Array of indexes of the user's names to clear. Must not have any duplicates. |
|
||||||
|
| clear.pronouns | int[] | Array of indexes of the user's pronouns to clear. Must not have any duplicates. |
|
||||||
|
| clear.fields | int[] | Array of indexes of the user's fields to clear. Must not have any duplicates. |
|
||||||
|
| clear.custom_preferences | uuid[] | Array of custom preference IDs to clear. Must not have any duplicates. |
|
||||||
|
|
||||||
|
#### Response body
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ------------ | --------- | ---------------------------------- |
|
||||||
|
| audit_log_id | snowflake | The ID of the newly created action |
|
55
go.mod
55
go.mod
|
@ -10,30 +10,30 @@ require (
|
||||||
github.com/davidbyttow/govips/v2 v2.13.0
|
github.com/davidbyttow/govips/v2 v2.13.0
|
||||||
github.com/georgysavva/scany/v2 v2.0.0
|
github.com/georgysavva/scany/v2 v2.0.0
|
||||||
github.com/getsentry/sentry-go v0.25.0
|
github.com/getsentry/sentry-go v0.25.0
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.0.11
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/httprate v0.7.4
|
github.com/go-chi/httprate v0.8.0
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.5.0
|
||||||
github.com/jackc/pgx/v5 v5.4.3
|
github.com/jackc/pgx/v5 v5.5.1
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mediocregopher/radix/v4 v4.1.4
|
github.com/mediocregopher/radix/v4 v4.1.4
|
||||||
github.com/minio/minio-go/v7 v7.0.63
|
github.com/minio/minio-go/v7 v7.0.66
|
||||||
github.com/prometheus/client_golang v1.17.0
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/rs/xid v1.5.0
|
github.com/rs/xid v1.5.0
|
||||||
github.com/rubenv/sql-migrate v1.5.2
|
github.com/rubenv/sql-migrate v1.6.0
|
||||||
github.com/toshi0607/chi-prometheus v0.1.4
|
github.com/toshi0607/chi-prometheus v0.1.4
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
github.com/urfave/cli/v2 v2.27.0
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/oauth2 v0.13.0
|
golang.org/x/oauth2 v0.15.0
|
||||||
google.golang.org/api v0.148.0
|
google.golang.org/api v0.154.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/compute v1.23.2 // indirect
|
cloud.google.com/go/compute v1.23.3 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||||
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf // indirect
|
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
|
@ -41,18 +41,21 @@ require (
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.3.0 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/gorilla/websocket v1.5.0 // indirect
|
github.com/gorilla/websocket v1.5.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.2 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||||
|
@ -67,18 +70,22 @@ require (
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/tilinna/clock v1.1.0 // indirect
|
github.com/tilinna/clock v1.1.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.14.0 // indirect
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
golang.org/x/image v0.13.0 // indirect
|
golang.org/x/image v0.14.0 // indirect
|
||||||
golang.org/x/net v0.17.0 // indirect
|
golang.org/x/net v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.4.0 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/sys v0.13.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
|
||||||
google.golang.org/grpc v1.59.0 // indirect
|
google.golang.org/grpc v1.60.1 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
123
go.sum
123
go.sum
|
@ -1,6 +1,6 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go/compute v1.23.2 h1:nWEMDhgbBkBJjfpVySqU4jgWdc22PLR0o4vEexZHers=
|
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
||||||
cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4hFDcR04Rns=
|
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0=
|
emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0=
|
||||||
|
@ -37,25 +37,29 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU=
|
github.com/georgysavva/scany/v2 v2.0.0 h1:RGXqxDv4row7/FYoK8MRXAZXqoWF/NM+NP0q50k3DKU=
|
||||||
github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38=
|
github.com/georgysavva/scany/v2 v2.0.0/go.mod h1:sigOdh+0qb/+aOs3TVhehVT10p8qJL7K/Zhyz8vWo38=
|
||||||
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
|
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
|
||||||
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-chi/httprate v0.7.4 h1:a2GIjv8he9LRf3712zxxnRdckQCm7I8y8yQhkJ84V6M=
|
github.com/go-chi/httprate v0.8.0 h1:CyKng28yhGnlGXH9EDGC/Qizj29afJQSNW15W/yj34o=
|
||||||
github.com/go-chi/httprate v0.7.4/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
|
github.com/go-chi/httprate v0.8.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||||
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU=
|
|
||||||
github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0=
|
|
||||||
github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY=
|
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||||
|
@ -91,32 +95,31 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
|
||||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
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/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
@ -126,9 +129,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||||
github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
|
|
||||||
github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
|
|
||||||
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||||
|
@ -136,8 +136,8 @@ github.com/mediocregopher/radix/v4 v4.1.4 h1:Uze6DEbEAvL+VHXUEu/EDBTkUk5CLct5h3n
|
||||||
github.com/mediocregopher/radix/v4 v4.1.4/go.mod h1:ajchozX/6ELmydxWeWM6xCFHVpZ4+67LXHOTOVR0nCE=
|
github.com/mediocregopher/radix/v4 v4.1.4/go.mod h1:ajchozX/6ELmydxWeWM6xCFHVpZ4+67LXHOTOVR0nCE=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ=
|
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
|
||||||
github.com/minio/minio-go/v7 v7.0.63/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4=
|
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
|
||||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
@ -164,8 +164,8 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=
|
github.com/rubenv/sql-migrate v1.6.0 h1:IZpcTlAx/VKXphWEpwWJ7BaMq05tYtE80zYz+8a5Il8=
|
||||||
github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is=
|
github.com/rubenv/sql-migrate v1.6.0/go.mod h1:m3ilnKP7sNb4eYkLsp6cGdPOl4OBcXM6rcbzU+Oqc5k=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
@ -181,19 +181,28 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
|
github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
|
||||||
github.com/tilinna/clock v1.1.0 h1:6IQQQCo6KoBxVudv6gwtY8o4eDfhHo8ojA5dP0MfhSs=
|
github.com/tilinna/clock v1.1.0 h1:6IQQQCo6KoBxVudv6gwtY8o4eDfhHo8ojA5dP0MfhSs=
|
||||||
github.com/tilinna/clock v1.1.0/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
|
github.com/tilinna/clock v1.1.0/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao=
|
||||||
github.com/toshi0607/chi-prometheus v0.1.4 h1:5KpqJrmdvMvbfU0JiL9ghOTbe8S9sgHDCCQvXgnyoJo=
|
github.com/toshi0607/chi-prometheus v0.1.4 h1:5KpqJrmdvMvbfU0JiL9ghOTbe8S9sgHDCCQvXgnyoJo=
|
||||||
github.com/toshi0607/chi-prometheus v0.1.4/go.mod h1:E++tBjqpDsvGWjLYdcFd5rvqJ7HG8wwBux+M6gyIL/Q=
|
github.com/toshi0607/chi-prometheus v0.1.4/go.mod h1:E++tBjqpDsvGWjLYdcFd5rvqJ7HG8wwBux+M6gyIL/Q=
|
||||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
github.com/urfave/cli/v2 v2.27.0 h1:uNs1K8JwTFL84X68j5Fjny6hfANh9nTlJ6dRtZAFAHY=
|
||||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
github.com/urfave/cli/v2 v2.27.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
|
||||||
|
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||||
|
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||||
|
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||||
|
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
@ -205,12 +214,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
|
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
|
||||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
@ -225,17 +234,17 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
|
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
|
||||||
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
|
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -246,19 +255,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
@ -268,8 +277,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.148.0 h1:HBq4TZlN4/1pNcu0geJZ/Q50vIwIXT532UIMYoo0vOs=
|
google.golang.org/api v0.154.0 h1:X7QkVKZBskztmpPKWQXgjJRPA2dJYrL6r+sYPRLj050=
|
||||||
google.golang.org/api v0.148.0/go.mod h1:8/TBgwaKjfqTdacOJrOv2+2Q6fBDU1uHKK06oGSkxzU=
|
google.golang.org/api v0.154.0/go.mod h1:qhSMkM85hgqiokIYsrRyKxrjfBeIhgl4Z2JmeRkYylc=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||||
|
@ -277,15 +286,15 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a h1:a2MQQVoTo96JC9PMGtGBymLp7+/RzpFc2yX/9WfFg1c=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
|
||||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
@ -297,8 +306,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|
30
scripts/migrate/023_mod_log.sql
Normal file
30
scripts/migrate/023_mod_log.sql
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
-- 2023-12-27: Add moderation log
|
||||||
|
|
||||||
|
-- +migrate Up
|
||||||
|
|
||||||
|
create table audit_log (
|
||||||
|
id bigint primary key,
|
||||||
|
-- the target user of this action
|
||||||
|
-- this is *not* a foreign key. if the user is deleted the audit log entry should stay, just showing as "deleted user <id>"
|
||||||
|
target_user_id bigint not null,
|
||||||
|
-- the target member of this action
|
||||||
|
target_member_id bigint,
|
||||||
|
-- the moderator that took this action
|
||||||
|
-- as with target_user_id, the audit log entry should not be deleted if the moderator's account is.
|
||||||
|
moderator_id bigint not null,
|
||||||
|
-- the report this was in response to. may be null if this action was taken by a moderator not responding to a report.
|
||||||
|
report_id integer references reports (id) on delete set null,
|
||||||
|
|
||||||
|
-- the reason for this action. always set, but may be 'None' if the action taken was 'ignore'.
|
||||||
|
reason text not null default 'None',
|
||||||
|
action_taken text not null, -- enum, currently: 'ignore', 'warn', 'suspend'
|
||||||
|
cleared_data jsonb null -- backup of the cleared data. may be null if no data was cleared
|
||||||
|
);
|
||||||
|
|
||||||
|
create index audit_log_target_user_idx on audit_log (target_user_id);
|
||||||
|
create index audit_log_target_member_idx on audit_log (target_member_id);
|
||||||
|
create index audit_log_moderator_idx on audit_log (moderator_id);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
|
||||||
|
drop table audit_log;
|
Loading…
Reference in a new issue