mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-08-28 05:12:32 +08:00
fix 修复websocket在某些情况下不重连问题
fix 修复登录日志查看权限 feat 访问日志增加接口信息显示 perf 为所有orm的Insert操作增加OmitEmptyData选项
This commit is contained in:
@@ -60,7 +60,7 @@ func (s *sSysBlacklist) Edit(ctx context.Context, in *sysin.BlacklistEditInp) (e
|
||||
}
|
||||
|
||||
// 新增
|
||||
_, err = dao.SysBlacklist.Ctx(ctx).Data(in).Insert()
|
||||
_, err = dao.SysBlacklist.Ctx(ctx).Data(in).OmitEmptyData().Insert()
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,7 @@ func (s *sSysCron) Edit(ctx context.Context, in *sysin.CronEditInp) (err error)
|
||||
}
|
||||
|
||||
// 新增
|
||||
in.SysCron.Id, err = dao.SysCron.Ctx(ctx).Data(in).InsertAndGetId()
|
||||
in.SysCron.Id, err = dao.SysCron.Ctx(ctx).Data(in).OmitEmptyData().InsertAndGetId()
|
||||
if err != nil || in.SysCron.Id < 1 {
|
||||
return
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ func (s *sSysCronGroup) Edit(ctx context.Context, in *sysin.CronGroupEditInp) (e
|
||||
}
|
||||
|
||||
// 新增
|
||||
if _, err = dao.SysCronGroup.Ctx(ctx).Fields(sysin.CronGroupInsertFields{}).Data(in).Insert(); err != nil {
|
||||
if _, err = dao.SysCronGroup.Ctx(ctx).Fields(sysin.CronGroupInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
}
|
||||
return
|
||||
|
@@ -161,7 +161,7 @@ func (s *sSysCurdDemo) Edit(ctx context.Context, in *sysin.CurdDemoEditInp) (err
|
||||
in.CreatedBy = contexts.GetUserId(ctx)
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(sysin.CurdDemoInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增CURD列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
|
@@ -60,7 +60,7 @@ func (s *sSysDictData) Edit(ctx context.Context, in *sysin.DictDataEditInp) (err
|
||||
return gerror.Wrap(err, "类型选择无效,请检查")
|
||||
}
|
||||
|
||||
_, err = dao.SysDictData.Ctx(ctx).Fields(sysin.DictDataInsertFields{}).Data(in).Insert()
|
||||
_, err = dao.SysDictData.Ctx(ctx).Fields(sysin.DictDataInsertFields{}).Data(in).OmitEmptyData().Insert()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return err
|
||||
|
@@ -96,7 +96,7 @@ func (s *sSysDictType) Edit(ctx context.Context, in *sysin.DictTypeEditInp) (err
|
||||
}
|
||||
|
||||
// 新增
|
||||
if _, err = dao.SysDictType.Ctx(ctx).Fields(sysin.DictTypeInsertFields{}).Data(in).Insert(); err != nil {
|
||||
if _, err = dao.SysDictType.Ctx(ctx).Fields(sysin.DictTypeInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
}
|
||||
return
|
||||
|
@@ -61,7 +61,7 @@ func (s *sSysEmsLog) Edit(ctx context.Context, in *sysin.EmsLogEditInp) (err err
|
||||
}
|
||||
|
||||
// 新增
|
||||
_, err = dao.SysEmsLog.Ctx(ctx).Data(in).Insert()
|
||||
_, err = dao.SysEmsLog.Ctx(ctx).Data(in).OmitEmptyData().Insert()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ func (s *sSysEmsLog) Send(ctx context.Context, in *sysin.SendEmsInp) (err error)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
_, err = dao.SysEmsLog.Ctx(ctx).Data(data).Insert()
|
||||
_, err = dao.SysEmsLog.Ctx(ctx).Data(data).OmitEmptyData().Insert()
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -88,7 +88,7 @@ func (s *sSysGenCodes) Edit(ctx context.Context, in *sysin.GenCodesEditInp) (res
|
||||
in.MasterColumns = gjson.New("[]")
|
||||
in.Status = consts.GenCodesStatusWait
|
||||
in.CreatedAt = gtime.Now()
|
||||
id, err := dao.SysGenCodes.Ctx(ctx).Data(in).InsertAndGetId()
|
||||
id, err := dao.SysGenCodes.Ctx(ctx).Data(in).OmitEmptyData().InsertAndGetId()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return
|
||||
|
@@ -7,6 +7,8 @@ package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@@ -17,7 +19,9 @@ import (
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/global"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/library/hgorm/hook"
|
||||
"hotgo/internal/library/location"
|
||||
@@ -40,6 +44,11 @@ func init() {
|
||||
service.RegisterSysLog(NewSysLog())
|
||||
}
|
||||
|
||||
// Model 请求日志Orm模型
|
||||
func (s *sSysLog) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
return handler.Model(dao.SysLog.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// Export 导出
|
||||
func (s *sSysLog) Export(ctx context.Context, in *sysin.LogListInp) (err error) {
|
||||
// 导出格式
|
||||
@@ -96,7 +105,7 @@ func (s *sSysLog) Export(ctx context.Context, in *sysin.LogListInp) (err error)
|
||||
|
||||
// RealWrite 真实写入
|
||||
func (s *sSysLog) RealWrite(ctx context.Context, log entity.SysLog) (err error) {
|
||||
_, err = dao.SysLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(log).Insert()
|
||||
_, err = dao.SysLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(log).Unscoped().OmitEmptyData().Insert()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,6 +180,10 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
|
||||
}
|
||||
}
|
||||
|
||||
if timestamp == 0 {
|
||||
timestamp = gtime.Timestamp()
|
||||
}
|
||||
|
||||
// 请求头
|
||||
if reqHeadersBytes, _ := gjson.New(request.Header).MarshalJSON(); len(reqHeadersBytes) > 0 {
|
||||
headerData = gjson.New(reqHeadersBytes)
|
||||
@@ -213,6 +226,8 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
|
||||
takeUpTime = tt
|
||||
}
|
||||
|
||||
headerData.MustSet("qqq", request.EnterTime.String())
|
||||
|
||||
data = entity.SysLog{
|
||||
AppId: appId,
|
||||
MerchantId: 0,
|
||||
@@ -234,13 +249,28 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
|
||||
UserAgent: request.Header.Get("User-Agent"),
|
||||
Status: consts.StatusEnabled,
|
||||
TakeUpTime: takeUpTime,
|
||||
UpdatedAt: gtime.Now(),
|
||||
CreatedAt: request.EnterTime,
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// View 获取指定字典类型信息
|
||||
// View 获取指定请求日志信息
|
||||
func (s *sSysLog) View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.LogViewModel, err error) {
|
||||
if err = dao.SysLog.Ctx(ctx).Handler(handler.FilterAuth).Hook(hook.CityLabel).Where("id", in.Id).Scan(&res); err != nil {
|
||||
mod := s.Model(ctx)
|
||||
|
||||
count, err := service.SysLoginLog().Model(ctx).
|
||||
LeftJoinOnFields(dao.SysLog.Table(), dao.SysLoginLog.Columns().ReqId, "=", dao.SysLog.Columns().ReqId).
|
||||
WherePrefix(dao.SysLog.Table(), dao.SysLog.Columns().Id, in.Id).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
mod = dao.SysLog.Ctx(ctx)
|
||||
}
|
||||
|
||||
if err = mod.Hook(hook.CityLabel).WherePri(in.Id).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return
|
||||
}
|
||||
@@ -249,6 +279,15 @@ func (s *sSysLog) View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.Lo
|
||||
return
|
||||
}
|
||||
|
||||
routes := global.LoadHTTPRoutes(ghttp.RequestFromCtx(ctx))
|
||||
key := global.GenRouteKey(res.Method, res.Url)
|
||||
route, ok := routes[key]
|
||||
if ok {
|
||||
res.Tags = route.Tags
|
||||
res.Summary = route.Summary
|
||||
res.Description = route.Description
|
||||
}
|
||||
|
||||
if simple.IsDemo(ctx) {
|
||||
res.HeaderData = gjson.New(`{
|
||||
"none": [
|
||||
@@ -259,15 +298,15 @@ func (s *sSysLog) View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.Lo
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
// Delete 删除请求日志
|
||||
func (s *sSysLog) Delete(ctx context.Context, in *sysin.LogDeleteInp) (err error) {
|
||||
_, err = dao.SysLog.Ctx(ctx).Handler(handler.FilterAuth).Where("id", in.Id).Delete()
|
||||
_, err = s.Model(ctx).WherePri(in.Id).Delete()
|
||||
return
|
||||
}
|
||||
|
||||
// List 列表
|
||||
// List 请求日志列表
|
||||
func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin.LogListModel, totalCount int, err error) {
|
||||
mod := dao.SysLog.Ctx(ctx).Handler(handler.FilterAuth).FieldsEx("get_data", "header_data", "post_data")
|
||||
mod := s.Model(ctx).FieldsEx("get_data", "header_data", "post_data")
|
||||
|
||||
// 访问路径
|
||||
if in.Url != "" {
|
||||
@@ -279,6 +318,11 @@ func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin
|
||||
mod = mod.Where("module", in.Module)
|
||||
}
|
||||
|
||||
// 链路ID
|
||||
if in.ReqId != "" {
|
||||
mod = mod.Where("req_id", in.ReqId)
|
||||
}
|
||||
|
||||
// 请求方式
|
||||
if in.Method != "" {
|
||||
mod = mod.Where("method", in.Method)
|
||||
@@ -305,8 +349,8 @@ func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin
|
||||
}
|
||||
|
||||
// 请求耗时
|
||||
if in.TakeUpTime > 0 {
|
||||
mod = mod.WhereGTE("take_up_time", in.TakeUpTime)
|
||||
if dict.HasOptionKey(consts.HTTPHandlerTimeOptions, in.TakeUpTime) {
|
||||
mod = mod.Where(fmt.Sprintf("`take_up_time` %v", in.TakeUpTime))
|
||||
}
|
||||
|
||||
totalCount, err = mod.Count()
|
||||
@@ -314,35 +358,40 @@ func (s *sSysLog) List(ctx context.Context, in *sysin.LogListInp) (list []*sysin
|
||||
return
|
||||
}
|
||||
|
||||
if err = mod.Page(in.Page, in.PerPage).Order("id desc").Scan(&list); err != nil {
|
||||
if err = mod.Page(in.Page, in.PerPage).Hook(hook.CityLabel).Order("id desc").Scan(&list); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(list); i++ {
|
||||
// 管理员
|
||||
if list[i].AppId == consts.AppAdmin {
|
||||
memberName, err := dao.AdminMember.Ctx(ctx).Fields("realname").Where("id", list[i].MemberId).Value()
|
||||
routes := global.LoadHTTPRoutes(ghttp.RequestFromCtx(ctx))
|
||||
for _, v := range list {
|
||||
if v.AppId == consts.AppAdmin {
|
||||
memberName, err := dao.AdminMember.Ctx(ctx).Fields("realname").WherePri(v.MemberId).Value()
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return list, totalCount, err
|
||||
}
|
||||
list[i].MemberName = memberName.String()
|
||||
v.MemberName = memberName.String()
|
||||
}
|
||||
|
||||
// 接口
|
||||
// ...
|
||||
|
||||
if list[i].MemberName == "" {
|
||||
list[i].MemberName = "游客"
|
||||
if v.MemberName == "" {
|
||||
v.MemberName = "游客"
|
||||
}
|
||||
|
||||
// 截取请求url路径
|
||||
if gstr.Contains(list[i].Url, "?") {
|
||||
list[i].Url = gstr.StrTillEx(list[i].Url, "?")
|
||||
if gstr.Contains(v.Url, "?") {
|
||||
v.Url = gstr.StrTillEx(v.Url, "?")
|
||||
}
|
||||
|
||||
key := global.GenRouteKey(v.Method, v.Url)
|
||||
route, ok := routes[key]
|
||||
if ok {
|
||||
v.Tags = route.Tags
|
||||
v.Summary = route.Summary
|
||||
v.Description = route.Description
|
||||
}
|
||||
|
||||
if simple.IsDemo(ctx) {
|
||||
list[i].HeaderData = gjson.New(`{
|
||||
v.HeaderData = gjson.New(`{
|
||||
"none": [
|
||||
"` + consts.DemoTips + `"
|
||||
]
|
||||
|
@@ -43,13 +43,13 @@ func init() {
|
||||
}
|
||||
|
||||
// Model 登录日志Orm模型
|
||||
func (s *sSysLoginLog) Model(ctx context.Context) *gdb.Model {
|
||||
return dao.SysLoginLog.Ctx(ctx)
|
||||
func (s *sSysLoginLog) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
return handler.Model(dao.SysLoginLog.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 获取登录日志列表
|
||||
func (s *sSysLoginLog) List(ctx context.Context, in *sysin.LoginLogListInp) (list []*sysin.LoginLogListModel, totalCount int, err error) {
|
||||
mod := dao.SysLoginLog.Ctx(ctx)
|
||||
mod := s.Model(ctx)
|
||||
|
||||
// 查询状态
|
||||
if in.Status > 0 {
|
||||
@@ -76,7 +76,7 @@ func (s *sSysLoginLog) List(ctx context.Context, in *sysin.LoginLogListInp) (lis
|
||||
return
|
||||
}
|
||||
|
||||
if err = mod.Fields(sysin.LoginLogListModel{}).Hook(hook.CityLabel).Handler(handler.FilterAuth).Page(in.Page, in.PerPage).OrderDesc(dao.SysLoginLog.Columns().Id).Scan(&list); err != nil {
|
||||
if err = mod.Fields(sysin.LoginLogListModel{}).Hook(hook.CityLabel).Page(in.Page, in.PerPage).OrderDesc(dao.SysLoginLog.Columns().Id).Scan(&list); err != nil {
|
||||
err = gerror.Wrap(err, consts.ErrorORM)
|
||||
return list, totalCount, err
|
||||
}
|
||||
@@ -121,13 +121,7 @@ func (s *sSysLoginLog) Export(ctx context.Context, in *sysin.LoginLogListInp) (e
|
||||
|
||||
// Delete 删除登录日志
|
||||
func (s *sSysLoginLog) Delete(ctx context.Context, in *sysin.LoginLogDeleteInp) (err error) {
|
||||
_, err = dao.SysLoginLog.Ctx(ctx).Where(dao.SysLoginLog.Columns().Id, in.Id).Delete()
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取登录日志指定信息
|
||||
func (s *sSysLoginLog) View(ctx context.Context, in *sysin.LoginLogViewInp) (res *sysin.LoginLogViewModel, err error) {
|
||||
err = dao.SysLoginLog.Ctx(ctx).Where(dao.SysLoginLog.Columns().Id, in.Id).Scan(&res)
|
||||
_, err = s.Model(ctx).WherePri(in.Id).Delete()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,6 +175,6 @@ func (s *sSysLoginLog) Push(ctx context.Context, in *sysin.LoginLogPushInp) {
|
||||
|
||||
// RealWrite 真实写入
|
||||
func (s *sSysLoginLog) RealWrite(ctx context.Context, models entity.SysLoginLog) (err error) {
|
||||
_, err = dao.SysLoginLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(models).Insert()
|
||||
_, err = dao.SysLoginLog.Ctx(ctx).Data(models).OmitEmptyData().Insert()
|
||||
return
|
||||
}
|
||||
|
@@ -113,7 +113,7 @@ func (s *sSysNormalTreeDemo) Edit(ctx context.Context, in *sysin.NormalTreeDemoE
|
||||
in.CreatedBy = contexts.GetUserId(ctx)
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(sysin.NormalTreeDemoInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增普通树表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
@@ -173,4 +173,4 @@ func (s *sSysNormalTreeDemo) TreeOption(ctx context.Context) (nodes []tree.Node,
|
||||
nodes[i] = v
|
||||
}
|
||||
return tree.ListToTree(0, nodes)
|
||||
}
|
||||
}
|
||||
|
@@ -113,7 +113,7 @@ func (s *sSysOptionTreeDemo) Edit(ctx context.Context, in *sysin.OptionTreeDemoE
|
||||
in.CreatedBy = contexts.GetUserId(ctx)
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(sysin.OptionTreeDemoInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增选项树表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
@@ -173,4 +173,4 @@ func (s *sSysOptionTreeDemo) TreeOption(ctx context.Context) (nodes []tree.Node,
|
||||
nodes[i] = v
|
||||
}
|
||||
return tree.ListToTree(0, nodes)
|
||||
}
|
||||
}
|
||||
|
@@ -92,7 +92,7 @@ func (s *sSysProvinces) Edit(ctx context.Context, in *sysin.ProvincesEditInp) (e
|
||||
}
|
||||
|
||||
// 新增
|
||||
if _, err = dao.SysProvinces.Ctx(ctx).Fields(sysin.ProvincesInsertFields{}).Data(in).Insert(); err != nil {
|
||||
if _, err = dao.SysProvinces.Ctx(ctx).Fields(sysin.ProvincesInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增省市区数据失败!")
|
||||
}
|
||||
return
|
||||
|
@@ -142,7 +142,7 @@ func (s *sSysServeLicense) Edit(ctx context.Context, in *sysin.ServeLicenseEditI
|
||||
}
|
||||
|
||||
// 新增
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Fields(sysin.ServeLicenseInsertFields{}).Data(in).Insert(); err != nil {
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).Fields(sysin.ServeLicenseInsertFields{}).Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增服务许可证失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
|
@@ -126,6 +126,6 @@ func (s *sSysServeLog) View(ctx context.Context, in *sysin.ServeLogViewInp) (res
|
||||
|
||||
// RealWrite 真实写入
|
||||
func (s *sSysServeLog) RealWrite(ctx context.Context, models entity.SysServeLog) (err error) {
|
||||
_, err = dao.SysServeLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(models).Insert()
|
||||
_, err = dao.SysServeLog.Ctx(ctx).FieldsEx(dao.SysLog.Columns().Id).Data(models).OmitEmptyData().Insert()
|
||||
return
|
||||
}
|
||||
|
@@ -138,7 +138,7 @@ func (s *sSysSmsLog) SendCode(ctx context.Context, in *sysin.SendCodeInp) (err e
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
_, err = dao.SysSmsLog.Ctx(ctx).Data(data).Insert()
|
||||
_, err = dao.SysSmsLog.Ctx(ctx).Data(data).OmitEmptyData().Insert()
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -98,7 +98,7 @@ func (s *sSysTestCategory) Edit(ctx context.Context, in *sysin.TestCategoryEditI
|
||||
// 新增
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(sysin.TestCategoryInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
Data(in).OmitEmptyData().Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增测试分类失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
@@ -164,4 +164,4 @@ func (s *sSysTestCategory) Option(ctx context.Context) (opts []*model.Option, er
|
||||
opts[k] = dict.GenHashOption(v.Id, gconv.String(v.Name))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user