feat: activity log backend foundation
This commit is contained in:
1 parent
8907bb49fe
commit
50b1649965
74 files changed
+5949
-239
No files matched your search
+126
-90
@@ -3,12 +3,14 @@ package api
|
||||
import (
|
||||
domainAPIKey "RenewCMS/internal/domain/apikey"
|
||||
domainArticle "RenewCMS/internal/domain/article"
|
||||
domainAudit "RenewCMS/internal/domain/audit"
|
||||
domainImage "RenewCMS/internal/domain/image"
|
||||
domainInvite "RenewCMS/internal/domain/invite"
|
||||
domainMail "RenewCMS/internal/domain/mail"
|
||||
domainPurge "RenewCMS/internal/domain/purge"
|
||||
domainUpdate "RenewCMS/internal/domain/update"
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/auditing"
|
||||
"RenewCMS/internal/infrastructure/config"
|
||||
"RenewCMS/internal/infrastructure/hashing"
|
||||
"RenewCMS/internal/infrastructure/mailer"
|
||||
@@ -19,6 +21,7 @@ import (
|
||||
"RenewCMS/internal/infrastructure/storage"
|
||||
"RenewCMS/internal/infrastructure/update"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -32,52 +35,73 @@ import (
|
||||
)
|
||||
|
||||
type UseCases struct {
|
||||
apiKeyAuthOnce sync.Once
|
||||
apiKeyAuthMiddleware func(http.Handler) http.Handler
|
||||
Token *jwtauth.JWTAuth
|
||||
PurgeScheduler *scheduler.Runner
|
||||
CreateArticleUseCase *useCases.CreateArticleUseCase
|
||||
GetArticleUseCase *useCases.GetArticleUseCase
|
||||
ListArticlesUseCase *useCases.ListArticlesUseCase
|
||||
UpdateArticleUseCase *useCases.UpdateArticleUseCase
|
||||
DeleteArticleUseCase *useCases.DeleteArticleUseCase
|
||||
GetUserUseCase *useCases.GetUserUseCase
|
||||
CreateUserUseCase *useCases.CreateUserUseCase
|
||||
AuthenticateUserUseCase *useCases.AuthenticateUserUseCase
|
||||
VerifyUserUseCase *useCases.VerifyUserUseCase
|
||||
CancelRegistrationUseCase *useCases.CancelRegistrationUseCase
|
||||
RequestPasswordResetUseCase *useCases.RequestPasswordResetUseCase
|
||||
ResetPasswordUseCase *useCases.ResetPasswordUseCase
|
||||
UpdateUserProfileUseCase *useCases.UpdateUserProfileUseCase
|
||||
ListUsersUseCase *useCases.ListUsersUseCase
|
||||
CreateInviteUseCase *useCases.CreateInviteUseCase
|
||||
CancelInviteUseCase *useCases.CancelInviteUseCase
|
||||
RenewInviteUseCase *useCases.RenewInviteUseCase
|
||||
ListUsersAndInvitesUseCase *useCases.ListUsersAndInvitesUseCase
|
||||
GetInviteUseCase *useCases.GetInviteUseCase
|
||||
AcceptInviteUseCase *useCases.AcceptInviteUseCase
|
||||
SendMailUseCase *useCases.SendMailUseCase
|
||||
CreateImageUseCase *useCases.CreateImageUseCase
|
||||
OpenMediaUseCase *useCases.OpenMediaUseCase
|
||||
ListMediaUseCase *useCases.ListMediaUseCase
|
||||
RenameMediaUseCase *useCases.RenameMediaUseCase
|
||||
DeleteMediaUseCase *useCases.DeleteMediaUseCase
|
||||
MergeMediaUseCase *useCases.MergeMediaUseCase
|
||||
GetMediaUsageUseCase *useCases.GetMediaUsageUseCase
|
||||
GetPurgeSettingsUseCase *useCases.GetPurgeSettingsUseCase
|
||||
UpdatePurgeSettingsUseCase *useCases.UpdatePurgeSettingsUseCase
|
||||
PreviewPurgeUseCase *useCases.PreviewPurgeUseCase
|
||||
RunPurgeUseCase *useCases.RunPurgeUseCase
|
||||
GetSettingsUseCase *useCases.GetSettingsUseCase
|
||||
GetUpdateStatusUseCase *useCases.GetUpdateStatusUseCase
|
||||
CheckForUpdateUseCase *useCases.CheckForUpdateUseCase
|
||||
StartUpdateDownloadUseCase *useCases.StartUpdateDownloadUseCase
|
||||
RequestRestartUseCase *useCases.RequestRestartUseCase
|
||||
RecordRunningInfoUseCase *useCases.RecordRunningInfoUseCase
|
||||
CreateAPIKeyUseCase *useCases.CreateAPIKeyUseCase
|
||||
ListAPIKeysUseCase *useCases.ListAPIKeysUseCase
|
||||
RevokeAPIKeyUseCase *useCases.RevokeAPIKeyUseCase
|
||||
AuthenticateAPIKeyUseCase *useCases.AuthenticateAPIKeyUseCase
|
||||
apiKeyAuthOnce sync.Once
|
||||
apiKeyAuthMiddleware func(http.Handler) http.Handler
|
||||
Token *jwtauth.JWTAuth
|
||||
PurgeScheduler *scheduler.Runner
|
||||
ActivityCleanupScheduler *scheduler.Runner
|
||||
CreateArticleUseCase *useCases.CreateArticleUseCase
|
||||
GetArticleUseCase *useCases.GetArticleUseCase
|
||||
ListArticlesUseCase *useCases.ListArticlesUseCase
|
||||
UpdateArticleUseCase *useCases.UpdateArticleUseCase
|
||||
DeleteArticleUseCase *useCases.DeleteArticleUseCase
|
||||
GetUserUseCase *useCases.GetUserUseCase
|
||||
CreateUserUseCase *useCases.CreateUserUseCase
|
||||
AuthenticateUserUseCase *useCases.AuthenticateUserUseCase
|
||||
VerifyUserUseCase *useCases.VerifyUserUseCase
|
||||
CancelRegistrationUseCase *useCases.CancelRegistrationUseCase
|
||||
RequestPasswordResetUseCase *useCases.RequestPasswordResetUseCase
|
||||
ResetPasswordUseCase *useCases.ResetPasswordUseCase
|
||||
UpdateUserProfileUseCase *useCases.UpdateUserProfileUseCase
|
||||
ListUsersUseCase *useCases.ListUsersUseCase
|
||||
CreateInviteUseCase *useCases.CreateInviteUseCase
|
||||
CancelInviteUseCase *useCases.CancelInviteUseCase
|
||||
RenewInviteUseCase *useCases.RenewInviteUseCase
|
||||
ListUsersAndInvitesUseCase *useCases.ListUsersAndInvitesUseCase
|
||||
GetInviteUseCase *useCases.GetInviteUseCase
|
||||
AcceptInviteUseCase *useCases.AcceptInviteUseCase
|
||||
SendMailUseCase *useCases.SendMailUseCase
|
||||
CreateImageUseCase *useCases.CreateImageUseCase
|
||||
OpenMediaUseCase *useCases.OpenMediaUseCase
|
||||
ListMediaUseCase *useCases.ListMediaUseCase
|
||||
RenameMediaUseCase *useCases.RenameMediaUseCase
|
||||
DeleteMediaUseCase *useCases.DeleteMediaUseCase
|
||||
MergeMediaUseCase *useCases.MergeMediaUseCase
|
||||
GetMediaUsageUseCase *useCases.GetMediaUsageUseCase
|
||||
GetPurgeSettingsUseCase *useCases.GetPurgeSettingsUseCase
|
||||
UpdatePurgeSettingsUseCase *useCases.UpdatePurgeSettingsUseCase
|
||||
PreviewPurgeUseCase *useCases.PreviewPurgeUseCase
|
||||
RunPurgeUseCase *useCases.RunPurgeUseCase
|
||||
GetSettingsUseCase *useCases.GetSettingsUseCase
|
||||
GetUpdateStatusUseCase *useCases.GetUpdateStatusUseCase
|
||||
CheckForUpdateUseCase *useCases.CheckForUpdateUseCase
|
||||
StartUpdateDownloadUseCase *useCases.StartUpdateDownloadUseCase
|
||||
RequestRestartUseCase *useCases.RequestRestartUseCase
|
||||
RecordRunningInfoUseCase *useCases.RecordRunningInfoUseCase
|
||||
CreateAPIKeyUseCase *useCases.CreateAPIKeyUseCase
|
||||
ListAPIKeysUseCase *useCases.ListAPIKeysUseCase
|
||||
RevokeAPIKeyUseCase *useCases.RevokeAPIKeyUseCase
|
||||
AuthenticateAPIKeyUseCase *useCases.AuthenticateAPIKeyUseCase
|
||||
ListActivityUseCase *useCases.ListActivityUseCase
|
||||
GetActivitySummaryUseCase *useCases.GetActivitySummaryUseCase
|
||||
ExportActivityUseCase *useCases.ExportActivityUseCase
|
||||
GetActivityRetentionUseCase *useCases.GetActivityRetentionUseCase
|
||||
UpdateActivityRetentionUseCase *useCases.UpdateActivityRetentionUseCase
|
||||
}
|
||||
|
||||
func (c *UseCases) RunSchedulers(ctx context.Context) {
|
||||
var running sync.WaitGroup
|
||||
for _, runner := range []*scheduler.Runner{c.PurgeScheduler, c.ActivityCleanupScheduler} {
|
||||
if runner == nil {
|
||||
continue
|
||||
}
|
||||
running.Add(1)
|
||||
go func() {
|
||||
defer running.Done()
|
||||
runner.Run(ctx)
|
||||
}()
|
||||
}
|
||||
running.Wait()
|
||||
}
|
||||
|
||||
type Repositories struct {
|
||||
@@ -87,6 +111,8 @@ type Repositories struct {
|
||||
InviteRepo domainInvite.Repository
|
||||
APIKeyRepo domainAPIKey.Repository
|
||||
PurgeSettingsRepo domainPurge.Repository
|
||||
AuditRepo domainAudit.Repository
|
||||
AuditSettingsRepo domainAudit.SettingsRepository
|
||||
MailRepo domainMail.Repository
|
||||
ReleaseFeed domainUpdate.ReleaseFeed
|
||||
UpdateStore domainUpdate.StatusStore
|
||||
@@ -217,6 +243,8 @@ func getRepositories(connection *database.Connection, settings config.Settings)
|
||||
InviteRepo: persistence.NewInviteRepository(db),
|
||||
APIKeyRepo: persistence.NewAPIKeyRepository(db),
|
||||
PurgeSettingsRepo: persistence.NewPurgeSettingsRepository(db),
|
||||
AuditRepo: persistence.NewAuditEventRepository(db),
|
||||
AuditSettingsRepo: persistence.NewAuditSettingsRepository(db),
|
||||
MailRepo: mailer.NewMailRepository(settings.Smtp),
|
||||
ReleaseFeed: update.NewGiteaReleaseFeed(settings.UpdateFeedURL),
|
||||
UpdateStore: update.NewJSONStatusStore(filepath.Join(dataDir, updateStatusFileName)),
|
||||
@@ -240,52 +268,60 @@ func getUseCases(repos *Repositories, imageStorage domainImage.Storage, settings
|
||||
|
||||
runPurge := useCases.NewRunPurgeUseCase(repos.ImageRepo, repos.ArticleRepo, mergeMedia, repos.PurgeSettingsRepo, time.Now, log.Default())
|
||||
purgeScheduler := useCases.NewPurgeSchedulerUseCase(repos.PurgeSettingsRepo, runPurge)
|
||||
recorder := auditing.NewRecorder(repos.AuditRepo, time.Now, log.Default())
|
||||
activityCleanup := useCases.NewCleanupActivityUseCase(repos.AuditRepo, repos.AuditSettingsRepo, recorder)
|
||||
|
||||
return &UseCases{
|
||||
PurgeScheduler: scheduler.NewSystemRunner(purgeScheduler, log.Default()),
|
||||
Token: jwtauth.New("HS256", []byte(settings.JWTSecret), nil),
|
||||
CreateArticleUseCase: useCases.NewCreateArticleUseCase(repos.ArticleRepo, renderer),
|
||||
GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo, renderer),
|
||||
ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo, renderer),
|
||||
UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo, renderer),
|
||||
DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo),
|
||||
GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo),
|
||||
CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo, hasher, repos.MailRepo, settings.PublicURL),
|
||||
AuthenticateUserUseCase: useCases.NewAuthenticateUserUseCase(repos.UserRepo, hasher),
|
||||
VerifyUserUseCase: useCases.NewVerifyUserUseCase(repos.UserRepo, hasher),
|
||||
CancelRegistrationUseCase: useCases.NewCancelRegistrationUseCase(repos.UserRepo),
|
||||
RequestPasswordResetUseCase: useCases.NewRequestPasswordResetUseCase(repos.UserRepo, hasher, repos.MailRepo, settings.PublicURL),
|
||||
ResetPasswordUseCase: useCases.NewResetPasswordUseCase(repos.UserRepo, hasher, repos.APIKeyRepo, time.Now),
|
||||
UpdateUserProfileUseCase: useCases.NewUpdateUserProfileUseCase(repos.UserRepo),
|
||||
ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo),
|
||||
CreateInviteUseCase: useCases.NewCreateInviteUseCase(repos.UserRepo, repos.InviteRepo, hasher, repos.MailRepo, settings.PublicURL),
|
||||
CancelInviteUseCase: useCases.NewCancelInviteUseCase(repos.InviteRepo),
|
||||
RenewInviteUseCase: useCases.NewRenewInviteUseCase(repos.UserRepo, repos.InviteRepo, hasher, repos.MailRepo, settings.PublicURL),
|
||||
ListUsersAndInvitesUseCase: useCases.NewListUsersAndInvitesUseCase(repos.UserRepo, repos.InviteRepo),
|
||||
GetInviteUseCase: useCases.NewGetInviteUseCase(repos.InviteRepo, hasher),
|
||||
AcceptInviteUseCase: useCases.NewAcceptInviteUseCase(repos.UserRepo, repos.InviteRepo, hasher),
|
||||
SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo),
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo, imageStorage),
|
||||
OpenMediaUseCase: useCases.NewOpenMediaUseCase(imageStorage),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(repos.ImageRepo, repos.ArticleRepo, repos.UserRepo),
|
||||
RenameMediaUseCase: useCases.NewRenameMediaUseCase(repos.ImageRepo),
|
||||
DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(repos.ImageRepo, imageStorage),
|
||||
MergeMediaUseCase: mergeMedia,
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(repos.ImageRepo, repos.ArticleRepo),
|
||||
GetPurgeSettingsUseCase: useCases.NewGetPurgeSettingsUseCase(repos.PurgeSettingsRepo),
|
||||
UpdatePurgeSettingsUseCase: useCases.NewUpdatePurgeSettingsUseCase(repos.PurgeSettingsRepo, time.Now),
|
||||
PreviewPurgeUseCase: useCases.NewPreviewPurgeUseCase(repos.ImageRepo, repos.ArticleRepo),
|
||||
RunPurgeUseCase: runPurge,
|
||||
GetSettingsUseCase: useCases.NewGetSettingsUseCase(settings.Version),
|
||||
GetUpdateStatusUseCase: useCases.NewGetUpdateStatusUseCase(repos.UpdateStore, repos.UpdateJobs, repos.UpdateHelper, repos.UpdateResults, repos.RestartRequests, settings.Version, settings.Container, time.Now),
|
||||
CheckForUpdateUseCase: useCases.NewCheckForUpdateUseCase(repos.ReleaseFeed, repos.UpdateStore, settings.Version, time.Now),
|
||||
StartUpdateDownloadUseCase: startUpdateDownloadUseCase(repos, settings),
|
||||
RequestRestartUseCase: useCases.NewRequestRestartUseCase(repos.UpdateStore, repos.UpdateJobs, repos.UpdateHelper, repos.UpdateResults, repos.RestartRequests, settings.Version, time.Now),
|
||||
RecordRunningInfoUseCase: useCases.NewRecordRunningInfoUseCase(repos.RunningInfo, settings.Version, os.Getpid(), time.Now),
|
||||
CreateAPIKeyUseCase: useCases.NewCreateAPIKeyUseCase(repos.APIKeyRepo, keyGenerator, time.Now),
|
||||
ListAPIKeysUseCase: useCases.NewListAPIKeysUseCase(repos.APIKeyRepo, repos.UserRepo, time.Now),
|
||||
RevokeAPIKeyUseCase: useCases.NewRevokeAPIKeyUseCase(repos.APIKeyRepo, repos.UserRepo, time.Now),
|
||||
AuthenticateAPIKeyUseCase: useCases.NewAuthenticateAPIKeyUseCase(repos.APIKeyRepo, keyGenerator, repos.UserRepo, time.Now),
|
||||
PurgeScheduler: scheduler.NewSystemRunner(purgeScheduler, log.Default()),
|
||||
ActivityCleanupScheduler: scheduler.NewSystemCleanupRunner(activityCleanup, log.Default()),
|
||||
Token: jwtauth.New("HS256", []byte(settings.JWTSecret), nil),
|
||||
CreateArticleUseCase: useCases.NewCreateArticleUseCase(repos.ArticleRepo, renderer),
|
||||
GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo, renderer),
|
||||
ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo, renderer),
|
||||
UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo, renderer),
|
||||
DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo),
|
||||
GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo),
|
||||
CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo, hasher, repos.MailRepo, settings.PublicURL),
|
||||
AuthenticateUserUseCase: useCases.NewAuthenticateUserUseCase(repos.UserRepo, hasher, recorder),
|
||||
VerifyUserUseCase: useCases.NewVerifyUserUseCase(repos.UserRepo, hasher),
|
||||
CancelRegistrationUseCase: useCases.NewCancelRegistrationUseCase(repos.UserRepo),
|
||||
RequestPasswordResetUseCase: useCases.NewRequestPasswordResetUseCase(repos.UserRepo, hasher, repos.MailRepo, settings.PublicURL, recorder),
|
||||
ResetPasswordUseCase: useCases.NewResetPasswordUseCase(repos.UserRepo, hasher, repos.APIKeyRepo, time.Now, recorder),
|
||||
UpdateUserProfileUseCase: useCases.NewUpdateUserProfileUseCase(repos.UserRepo),
|
||||
ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo),
|
||||
CreateInviteUseCase: useCases.NewCreateInviteUseCase(repos.UserRepo, repos.InviteRepo, hasher, repos.MailRepo, settings.PublicURL),
|
||||
CancelInviteUseCase: useCases.NewCancelInviteUseCase(repos.InviteRepo),
|
||||
RenewInviteUseCase: useCases.NewRenewInviteUseCase(repos.UserRepo, repos.InviteRepo, hasher, repos.MailRepo, settings.PublicURL),
|
||||
ListUsersAndInvitesUseCase: useCases.NewListUsersAndInvitesUseCase(repos.UserRepo, repos.InviteRepo),
|
||||
GetInviteUseCase: useCases.NewGetInviteUseCase(repos.InviteRepo, hasher),
|
||||
AcceptInviteUseCase: useCases.NewAcceptInviteUseCase(repos.UserRepo, repos.InviteRepo, hasher),
|
||||
SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo),
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo, imageStorage),
|
||||
OpenMediaUseCase: useCases.NewOpenMediaUseCase(imageStorage),
|
||||
ListMediaUseCase: useCases.NewListMediaUseCase(repos.ImageRepo, repos.ArticleRepo, repos.UserRepo),
|
||||
RenameMediaUseCase: useCases.NewRenameMediaUseCase(repos.ImageRepo),
|
||||
DeleteMediaUseCase: useCases.NewDeleteMediaUseCase(repos.ImageRepo, imageStorage),
|
||||
MergeMediaUseCase: mergeMedia,
|
||||
GetMediaUsageUseCase: useCases.NewGetMediaUsageUseCase(repos.ImageRepo, repos.ArticleRepo),
|
||||
GetPurgeSettingsUseCase: useCases.NewGetPurgeSettingsUseCase(repos.PurgeSettingsRepo),
|
||||
UpdatePurgeSettingsUseCase: useCases.NewUpdatePurgeSettingsUseCase(repos.PurgeSettingsRepo, time.Now),
|
||||
PreviewPurgeUseCase: useCases.NewPreviewPurgeUseCase(repos.ImageRepo, repos.ArticleRepo),
|
||||
RunPurgeUseCase: runPurge,
|
||||
GetSettingsUseCase: useCases.NewGetSettingsUseCase(settings.Version),
|
||||
GetUpdateStatusUseCase: useCases.NewGetUpdateStatusUseCase(repos.UpdateStore, repos.UpdateJobs, repos.UpdateHelper, repos.UpdateResults, repos.RestartRequests, settings.Version, settings.Container, time.Now),
|
||||
CheckForUpdateUseCase: useCases.NewCheckForUpdateUseCase(repos.ReleaseFeed, repos.UpdateStore, settings.Version, time.Now),
|
||||
StartUpdateDownloadUseCase: startUpdateDownloadUseCase(repos, settings),
|
||||
RequestRestartUseCase: useCases.NewRequestRestartUseCase(repos.UpdateStore, repos.UpdateJobs, repos.UpdateHelper, repos.UpdateResults, repos.RestartRequests, settings.Version, time.Now),
|
||||
RecordRunningInfoUseCase: useCases.NewRecordRunningInfoUseCase(repos.RunningInfo, settings.Version, os.Getpid(), time.Now),
|
||||
CreateAPIKeyUseCase: useCases.NewCreateAPIKeyUseCase(repos.APIKeyRepo, keyGenerator, time.Now, recorder),
|
||||
ListAPIKeysUseCase: useCases.NewListAPIKeysUseCase(repos.APIKeyRepo, repos.UserRepo, time.Now),
|
||||
RevokeAPIKeyUseCase: useCases.NewRevokeAPIKeyUseCase(repos.APIKeyRepo, repos.UserRepo, time.Now, recorder),
|
||||
AuthenticateAPIKeyUseCase: useCases.NewAuthenticateAPIKeyUseCase(repos.APIKeyRepo, keyGenerator, repos.UserRepo, time.Now),
|
||||
ListActivityUseCase: useCases.NewListActivityUseCase(repos.AuditRepo, repos.UserRepo, repos.ArticleRepo, repos.ImageRepo, repos.APIKeyRepo, time.Now),
|
||||
GetActivitySummaryUseCase: useCases.NewGetActivitySummaryUseCase(repos.AuditRepo, time.Now),
|
||||
ExportActivityUseCase: useCases.NewExportActivityUseCase(repos.AuditRepo, time.Now),
|
||||
GetActivityRetentionUseCase: useCases.NewGetActivityRetentionUseCase(repos.AuditSettingsRepo),
|
||||
UpdateActivityRetentionUseCase: useCases.NewUpdateActivityRetentionUseCase(repos.AuditSettingsRepo, recorder),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user